一、 vue双向绑定原理
实现mvvm主要包含两个方面,数据变化更新视图,视图变化更新数据。
视图更新数据很容易实现,可以通过事件监听即可,比如input标签监听 'input' 事件就可以实现了。 所以我们着重来分析vue的响应原理,即当数据改变,如何更新视图?
vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
以下只放部分代码,完整代码请戳 -> github.com/liaoyali/si…
实现过程:
1.实现一个Observer
1)Observer是一个数据监听器,实现它的核心方法就是利用Object.defineProperty( )。一般我们通过递归方法遍历所有属性值,从而对所有属性都进行监听,然后利用Object.defineProperty()对每个属性进行处理。
2)由于有很多个订阅者,所以我们还需要创建一个可以容纳订阅者的消息订阅器Dep,订阅器Dep主要负责收集订阅者
3)当属性变化的时候,执行set里的dep.notify()去通知相应的订阅者让他们执行对应的更新函数update()。
observer.js:
function Observer(data) {
this.data = data;
this.walk(data);
}
Observer.prototype = {
walk: function(data) {
Object.keys(data).forEach(function(key) {
this.defineReactive(data, key, data[key]);
}.bind(this));
},
defineReactive: function(data, key, val) {
observe(val); // 递归遍历所有子属性
let dep = new Dep();
Object.defineProperty(data, key, { // 1) 数据劫持
enumerable: true,
configurable: true,
get: function() {
if (Dep.target) {
dep.addSub(Dep.target); // 2.2) 在这里添加一个订阅者
}
return val;
},
set: function(newVal) {
if (val === newVal) {
return;
}
val = newVal;
console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”');
dep.notify(); // 3.1) 如果数据变化,通知所有订阅者
}
});
}
}
function observe(data) {
if (!data || typeof data !== 'object') {
return;
}
return new Observer(data);
};
function Dep() { // 2.1) 订阅器Dep
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() { // 3.2) 通知订阅者去更新
this.subs.forEach(function(sub) {
sub.update();
})
}
}
Dep.target = null;
2. 实现watcher
1)订阅者watcher在初始化的时候需要把自己添加进订阅器Dep中
之前在实现Observer的时候,在get函数里已具有添加订阅者watcher的操作,所以我们只要在订阅者watcher初始化的时候去触发对应的get函数从而执行添加订阅者即可(只要获取对应的属性值就可以被触发)。
2)自身还需要实现 update()方法,当observer.js监听到属性发生改变被通知更新的时候就执行watcher.js的update()从而更新
watcher.js:
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
this.value = this.get(); // 将自己添加到订阅器的操作
}
Watcher.prototype = {
get: function() {
Dep.target = this; //缓存自己
let value = this.vm.data[this.exp]; //强制执行监听器里的get函数 只要获取value就可以触发observer里的get
Dep.target = null; //释放自己
return value;
},
update: function() {
this.run();
},
run: function() {
let value = this.vm.data[this.exp]; //获取的新值
let oldVal = this.value; //旧值
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value);
}
},
}
3. 实现compile
observer和watcher已设置完成,需要再实现一个解析器compiler去做解析和绑定工作,
1.解析模板指令(比如{{name}}),并替换模板数据,初始化视图;
2.将模板指令对应的节点绑定对应的更新函数,初始化(new Watcher)相应的订阅器watcher。
compile.js部分代码:
function Compile(el, vm) {
this.vm = vm;
this.el = document.querySelector(el);
this.fragment = null;
this.init();
}
Compile.prototype = {
init: function() {
if (this.el) {
this.fragment = this.nodeToFragment(this.el);
this.compileElement(this.fragment);
this.el.appendChild(this.fragment);
} else {
console.log('Dom元素不存在');
}
},
nodeToFragment: function(el) {
let fragment = document.createDocumentFragment();
let child = el.firstChild;
while (child) {
// 将所有dom元素移入片段fragment
fragment.appendChild(child);
child = el.firstChild
}
return fragment;
},
<!--匹配模板指令-->
compileElement: function(el) {
let childNodes = el.childNodes;
let that = this;
[].slice.call(childNodes).forEach(function(node) {
let reg = /\{\{\s*(.*?)\s*\}\}/;
let text = node.textContent;
if (that.isElementNode(node)) {
that.compile(node);
} else if (that.isTextNode(node) && reg.test(text)) {
// 判断是否是符合资助形式{{}}的指令
that.compileText(node, reg.exec(text)[1]);
}
if (node.childNodes && node.childNodes.length) {
that.compileElement(node); // 继续递归遍历子节点
}
})
},
compile: function(node) {
let nodeAttrs = node.attributes;
Array.prototype.forEach.call(nodeAttrs, function(attr) {
let attrName = attr.name;
if (this.isDirective(attrName)) {
let exp = attr.value;
let dir = attrName.substring(2);
if (this.isEventDirective(dir)) { // 事件指令
this.compileEvent(node, this.vm, exp, dir);
} else { // v-model 指令
this.compileModel(node, this.vm, exp, dir);
}
node.removeAttribute(attrName);
}
}.bind(this));
},
<!--监听input输入-->
node.addEventListener('input', function(e) {
var newValue = e.target.value;
if (val === newValue) {
return;
}
self.vm[exp] = newValue;
val = newValue;
});
},
<!--生成订阅器watcher-->
compileText: function(node, exp) {
let initText = this.vm[exp];
this.updateText(node, initText); // 将初始化的数据初始化到视图中
new Watcher(this.vm, exp, function(value) {
// 生成订阅器并绑定更新函数
this.updateText(node, value);
}.bind(this));
},
...