1、目标实现
- 理解双向数据绑定原理;
- 实现 {{ }}、v-model和基本事件指令v-bind(:)、v-on(@);
- 新增属性的双向绑定处理;
2、双向数据绑定原理
vue实现对数据的双向绑定,通过对数据劫持结合发布者-订阅者模式实现的。
2.1 Object.defineProperty
vue通过Object.defineProperty来实现数据劫持,会对数据对象每个属性添加对应的get和set方法,对数据进行读取和赋值操作就分别调用get和set方法。
1 | Object.defineProperty(data, key, { |
我们可以将一些方法放到里面,从而完成对数据的监听(劫持)和视图的同步更新。
2.2 过程说明
实现双向数据绑定,首先要对数据进行数据监听,需要一个监听器Observer,监听所有属性。如果属性发生变化,会调用setter和getter,再去告诉订阅者Watcher是否需要更新。由于订阅者有很多个,我们需要一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理。还有,我们需要一个指令解析器Complie,对每个元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或绑定相应函数。当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。
3、实现Observer
Observer是一个数据监听器,核心方法是我们提到过的Object.defineProperty。如果要监听所有属性的话,则需要通过递归遍历,对每个子属性都defineProperty。
1 | /** |
4、实现Dep
在流程介绍中,我们需要创建一个可以订阅者的订阅器Dep,主要负责手机订阅者,属性变化的时候执行相应的订阅者,更新函数。下面稍加改造Observer,就可以插入我们的订阅器。
1 | Observer.prototype = { |
PS:将订阅器Dep添加到一个订阅者设计到getter里面,是为了让Watcher初始化进行触发。
5、实现Watcher
订阅者Watcher在初始化的时候需要将自己添加到订阅器Dep中,那该如何添加呢?我们已经知道监听器Observer是在get函数执行添加了订阅者Watcher的操作,所以我们只要在订阅者Watcher初始化的时候触发对应的get函数去执行添加订阅者操作。那么,怎样去触发get函数?很简单,只需获取对应的属性值就可以触发了,因为我们已经用Object.defineProperty监听了所有属性。vue在这里做了个技巧处理,就是咋我们添加订阅者的时候,做一个判断,判断是否是事先缓存好的Dep.target,在订阅者添加成功后,把target重置null即可。
1 | // ... |
到这里,其实已经实现了我们的双向数据绑定:能够根据初始数据初始化页面特定元素,同时当数据改变也能更新视图。
5、实现Compile
步骤4整个过程都能有去解析DOM节点,而是直接固定节点进行替换。接下来我们就来实现一个解析器,完成一些解析和绑定工作。
- 获取页面的DOM节点,遍历存入到文档碎片对象中;
- 解析出文本节点,匹配{{}}(暂时只做”{{}}”的解析),用初始化数据替换,并添加相应订阅者;
- 分离出节点的指令v-on、v-bind和v-model,绑定相应的事件和函数;
1 | // ... |
这样我们就可以调用指令v-bind、v-on和v-model。
1 | <head> |
5、其他
5.1 proxy代理data
可能注意到了,我们不管是在赋值还是取值,都是在myvm.data.someAttr上操作的,而在vue上我们习惯直接myvm.someAttr这种形式。怎样实现呢?同样,我们可以用Object.defineProperty对data所有属性做一个代理,即访问vue实例属性时,代理到data上。很简单,实现如下:
1 | /** |
5.2 parsePath
上面对于data的操作只是到对于简单的基本类型属性,对于对象属性的改变该怎么更新到位呢?其实,只要深度遍历对象属性路径,就可以找到要访问属性值。
1 | /** |
用这个方法替换我们的所有取值操作
vm[exp] => parsePath(vm, exp)
6、新增属性的双向数据绑定
6.1 给对象添加属性
Vue 不允许在已经创建的实例上动态添加新的根级响应式属性 (root-level reactive property)。然而它可以使用 Vue.set(object, key, value) 方法将响应属性添加到嵌套的对象上。
也就是我们需要在Vue原型上添加一个set方法去设置新添加的属性,新属性同样要进行监听和添加订阅者。
1 | /** |
6.1 给数组对象添加属性
把数组看成一个特殊的对象,就很容易理解了,对于unshift、push和splice变异方法是添加了对象的属性的,需要对新加的属性进行监听和添加订阅者。
1 | var arrKeys = ["push", "pop", "shift", "unshift", "splice", "sort", "reverse"]; |