双向数据绑定
vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
要实现mvvm的双向绑定,就必须要实现以下几点:
- 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者。
- 实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
- 实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
- mvvm入口函数,整合以上三者

Dep订阅者容器
维护一个数组,用来收集订阅者,数据变动触发notify,再调用订阅者的update方法。
CODE
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => {
sub.update();
});
}
}
Watcher订阅者
发出通知 dep.notify() => 触发订阅者的 update 方法 => 更新视图
首先,将自己赋给了一个全局变量 Dep.target;
其次,执行了 update 方法,进而执行了 get 方法,get 的方法读取了 vm 的访问器属性,从而触发了访问器属性的 get 方法,get 方法中将该 watcher 添加到了对应访问器属性的 dep 中;
再次,获取属性的值,然后更新视图。
最后,将 Dep.target 设为空。因为它是全局变量,也是 watcher 与 dep 关联的唯一桥梁,任何时刻都必须保证 Dep.target 只有一个值。
CODE
// 订阅/发布模式
class Watcher {
constructor(vm, node, name) {
Dep.target = this;
this.name = name;
this.node = node;
this.vm = vm;
this.update();
Dep.target = null;
}
update() {
this.get();
this.node.nodeValue = this.value;
}
// 获取data中的属性值
get() {
// 触发相应属性的get,添加到订阅者容器Dep
this.value = this.vm[this.name];
}
}
Observe监听数据
使用Obeject.defineProperty()监听属性变动,遍历需要监听的对象属性,都加上setter和getter。给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。
CODE
function observe(obj, vm) {
Object.keys(obj).forEach(key => {
defineReactive(vm, key, obj[key]);
});
}
function defineReactive(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
// 添加订阅者watcher到订阅者容器Dep
if (Dep.target) dep.addSub(Dep.target);
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
// 作为发布者发出通知
dep.notify();
}
});
}
Compile解析模板指令
将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。

CODE
function compile(node, vm) {
var reg = /\{\{(.*)\}\}/;
// 节点类型为元素
if (node.nodeType === 1) {
var attr = node.attributes;
// 解析属性
for (var i = 0; i < attr.length; i++) {
if (attr[i].nodeName == 'v-model') {
// 获取v-model绑定的属性名
var name = attr[i].nodeValue;
node.addEventListener('input', e => {
// 给相应的data属性赋值,进而触发该属性的set方法
vm[name] = e.target.value;
})
// 将data的值赋给该node
node.value = vm[name];
node.removeAttribute('v-model');
}
}
}
// 节点类型为text
if (node.nodeType === 3) {
if (reg.test(node.nodeValue)) {
// 获取匹配到的字符串
var name = RegExp.$1;
name = name.trim();
// 监听数据
new Watcher(vm, node, name);
}
}
}
DocumentFragment
DocumentFragment(文档片段)可以看作节点容器,它可以包含多个子节点,当我们将它插入到 DOM 中时,只有它的子节点会插入目标节点,所以把它看作一组节点的容器。使用 DocumentFragment 处理节点,速度和性能远远优于直接操作 DOM。Vue 进行编译时,就是将挂载目标的所有子节点劫持(真的是劫持,通过 append 方法,DOM 中的节点会被自动删除)到 DocumentFragment 中,经过一番处理后,再将 DocumentFragment 整体返回插入挂载目标。
CODE
function nodeToFragment(node, vm) {
var flag = document.createDocumentFragment();
var child;
while (child = node.firstChild) {
compile(child, vm);
// 劫持node的所有子节点
flag.appendChild(child);
}
return flag;
}
Vue数据绑定入口
CODE
class Vue {
constructor(options) {
this.data = options.data;
var data = this.data;
observe(data, this);
var id = options.el;
var dom = nodeToFragment(document.getElementById(id), this);
// 编译完成后,将dom返回到app中
document.getElementById('app').appendChild(dom);
}
}
https://www.cnblogs.com/tugenhua0707/p/10306793.html
双向数据绑定
vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
要实现mvvm的双向绑定,就必须要实现以下几点:
Dep订阅者容器
维护一个数组,用来收集订阅者,数据变动触发notify,再调用订阅者的update方法。
CODE
Watcher订阅者
发出通知 dep.notify() => 触发订阅者的 update 方法 => 更新视图
首先,将自己赋给了一个全局变量 Dep.target;
其次,执行了 update 方法,进而执行了 get 方法,get 的方法读取了 vm 的访问器属性,从而触发了访问器属性的 get 方法,get 方法中将该 watcher 添加到了对应访问器属性的 dep 中;
再次,获取属性的值,然后更新视图。
最后,将 Dep.target 设为空。因为它是全局变量,也是 watcher 与 dep 关联的唯一桥梁,任何时刻都必须保证 Dep.target 只有一个值。
CODE
Observe监听数据
使用
Obeject.defineProperty()监听属性变动,遍历需要监听的对象属性,都加上setter和getter。给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。CODE
Compile解析模板指令
将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
CODE
DocumentFragment
DocumentFragment(文档片段)可以看作节点容器,它可以包含多个子节点,当我们将它插入到 DOM 中时,只有它的子节点会插入目标节点,所以把它看作一组节点的容器。使用 DocumentFragment 处理节点,速度和性能远远优于直接操作 DOM。Vue 进行编译时,就是将挂载目标的所有子节点劫持(真的是劫持,通过 append 方法,DOM 中的节点会被自动删除)到 DocumentFragment 中,经过一番处理后,再将 DocumentFragment 整体返回插入挂载目标。
CODE
Vue数据绑定入口
CODE
https://www.cnblogs.com/tugenhua0707/p/10306793.html