Vue基本原理 (三)
3、响应式原理
核心思想:Object.defineProperty(obj, key, {set, get})
function defineReact(obj, key, value){
Object.defineProperty(obj, key, {
set: function(newValue){
console.log(`触发setter`);
value = newValue;
console.log(value);
},
get: function(){
console.log(`触发getter`);
return value;
}
});
}
这里是针对data数据的属性的响应式定义,但是如何去实现vue实例vm绑定data每个属性,通过以下方法:
function observe(obj, vm){
Object.keys(obj).forEach((key) => {
defineReact(vm, key, obj[key]);
})
}
vue的构造函数:
function Vue(options){
this.data = options.data;
let id = options.el;
observe(this.data, this); // 将每个data属相绑定到Vue的实例上this
}
通过以上我们可以实现Vue实例绑定data属性。
如何去实现Vue,通常我们实例化Vue是这样的:
var vm = new Vue({
el: 'container',
data: {
msg: 'Hello world!',
inpText: 'Input text'
}
});
console.log(vm.msg); // Hello world!
console.log(vm.inpText); // Input text
实现以上效果,我们必须在vue内部初始化虚拟Dom
function Vue(options){
this.data = options.data;
let id = options.el;
observe(this.data, this); // 将每个data属相绑定到Vue的实例上this
//------------------------添加以下代码
let container = document.getElementById(id);
let fragment = virtualDom(container, this); // 这里通过vm对象初始化
container.appendChild(fragment);
}
这是我们再对Vue进行实例化,则可以看到以下页面:

至此我们实现了dom的初始化,下一步我们在v-model元素添加监听事件,这样就可以通过view层的操作来修改vm对应的属性值。在compile编译的时候,可以准确的找到v-model属相元素,因此我们把监听事件添加到compile内部。
function compile(node, data){
let reg = /\{\{(.*)\}\}/g;
if(node.nodeType === 1){ // 标签
let attr = node.attributes;
for(let i = 0, len = attr.length; i < len; i++){
// console.log(attr[i].nodeName, attr[i].nodeValue);
if(attr[i].nodeName === 'v-model'){
let name = attr[i].nodeValue;
node.value = data[name];
// ------------------------添加监听事件
node.addEventListener('keyup', function(e){
data[name] = e.target.value;
}, false);
// -----------------------------------
}
}
if(node.hasChildNodes()){
node.childNodes.forEach((item) => {
compile(item, data);
});
}
}
if(node.nodeType === 3){ // 文本节点
if(reg.test(node.nodeValue)){
let name = RegExp.$1;
name = name.trim();
node.nodeValue = data[name];
}
}
}
这一步我们操作页面输入框,可以看到以下效果,证明监听事件添加有效。

var subscribe_1 = {
update: function(){
console.log('This is subscribe_1');
}
};
var subscribe_2 = {
update: function(){
console.log('This is subscribe_2');
}
};
var subscribe_3 = {
update: function(){
console.log('This is subscribe_3');
}
};
到这里我们已经实现了MVVM的,即Model -> vm -> View || View -> vm -> Model 中间桥梁就是vm实例对象。
4、观察者模式原理
观察者模式也称为发布者-订阅者模式,这样说应该会更容易理解,更加形象。
订阅者:
var subscribe_1 = {
update: function(){
console.log('This is subscribe_1');
}
};
var subscribe_2 = {
update: function(){
console.log('This is subscribe_2');
}
};
var subscribe_3 = {
update: function(){
console.log('This is subscribe_3');
}
};
三个订阅者都有update方法。
发布者:
function Publisher(){
this.subs = [subscribe_1, subscribe_2, subscribe_3]; // 添加订阅者
}
Publisher.prototype = {
constructor: Publisher,
notify: function(){
this.subs.forEach(function(sub){
sub.update();
})
}
};
发布者通过notify方法对订阅者广播,订阅者通过update来接受信息。
实例化publisher:
var publisher = new Publisher();
publisher.notify();

这里我们可以做一个中间件来处理发布者-订阅者模式:
var publisher = new Publisher();
var middleware = {
publish: function(){
publisher.notify();
}
};
middleware.publish();
5、观察者模式嵌入
到这一步,我们已经实现了:
1、修改v-model属性元素 -> 触发修改vm的属性值 -> 触发set
2、发布者添加订阅 -> notify分发订阅 -> 订阅者update数据
接下来我们要实现:更新视图,同时把订阅——发布者模式嵌入。
发布者:
function Publisher(){
this.subs = []; // 订阅者容器
}
Publisher.prototype = {
constructor: Publisher,
add: function(sub){
this.subs.push(sub); // 添加订阅者
},
notify: function(){
this.subs.forEach(function(sub){
sub.update(); // 发布订阅
});
}
};
订阅者:
考虑到要把订阅者绑定data的每个属性,来观察属性的变化,参数:name参数可以有compile中获取的name传参。由于传入的node节点类型分为两种,我们可以分为两订阅者来处理,同时也可以对node节点类型进行判断,通过switch分别处理。
function Subscriber(node, vm, name){
this.node = node;
this.vm = vm;
this.name = name;
}
Subscriber.prototype = {
constructor: Subscriber,
update: function(){
let vm = this.vm;
let node = this.node;
let name = this.name;
switch(this.node.nodeType){
case 1:
node.value = vm[name];
break;
case 3:
node.nodeValue = vm[name];
break;
default:
break;
}
}
};
我们要把订阅者添加到compile进行虚拟dom的初始化,替换掉原来的赋值:
function compile(node, data){
let reg = /\{\{(.*)\}\}/g;
if(node.nodeType === 1){ // 标签
let attr = node.attributes;
for(let i = 0, len = attr.length; i < len; i++){
// console.log(attr[i].nodeName, attr[i].nodeValue);
if(attr[i].nodeName === 'v-model'){
let name = attr[i].nodeValue;
// --------------------这里被替换掉
// node.value = data[name];
new Subscriber(node, data, name);
// ------------------------添加监听事件
node.addEventListener('keyup', function(e){
data[name] = e.target.value;
}, false);
}
}
if(node.hasChildNodes()){
node.childNodes.forEach((item) => {
compile(item, data);
});
}
}
if(node.nodeType === 3){ // 文本节点
if(reg.test(node.nodeValue)){
let name = RegExp.$1;
name = name.trim();
// ---------------------这里被替换掉
// node.nodeValue = data[name];
new Subscriber(node, data, name);
}
}
}
既然是对虚拟dom编译初始化,Subscriber要初始化,即Subscriber.update,因此要对Subscriber作进一步的处理:
function Subscriber(node, vm, name){
this.node = node;
this.vm = vm;
this.name = name;
this.update();
}
Subscriber.prototype = {
constructor: Subscriber,
update: function(){
let vm = this.vm;
let node = this.node;
let name = this.name;
switch(this.node.nodeType){
case 1:
node.value = vm[name];
break;
case 3:
node.nodeValue = vm[name];
break;
default:
break;
}
}
};
发布者添加到defineReact,来观察数据的变化:
function defineReact(data, key, value){
let publisher = new Publisher();
Object.defineProperty(data, key, {
set: function(newValue){
console.log(`触发setter`);
value = newValue;
console.log(value);
publisher.notify(); // 发布订阅
},
get: function(){
console.log(`触发getter`);
if(Publisher.global){ //这里为什么来添加判断条件,主要是让publisher.add只执行一次,初始化虚拟dom编译的时候来执行
publisher.add(Publisher.global); // 添加订阅者
}
return value;
}
});
}
这一步将订阅者添加到发布者容器内,对订阅者改造:
function Subscriber(node, vm, name){
Publisher.global = this;
this.node = node;
this.vm = vm;
this.name = name;
this.update();
Publisher.global = null;
}
Subscriber.prototype = {
constructor: Subscriber,
update: function(){
let vm = this.vm;
let node = this.node;
let name = this.name;
switch(this.node.nodeType){
case 1:
node.value = vm[name];
break;
case 3:
node.nodeValue = vm[name];
break;
default:
break;
}
}
};
点击这里=》 效果图+总结

浙公网安备 33010602011771号