实现数据绑定的做法有大致如下几种:
- 发布者-订阅者模式(backbone.js)
- 脏值检查(angular.js)
- 数据劫持(vue.js)
数据劫持双向绑定
vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
数据劫持是双向绑定各种方案中比较流行的一种,最著名的实现就是Vue。
基于数据劫持的双向绑定离不开Proxy与Object.defineProperty等方法对对象/对象属性的"劫持",我们要实现一个完整的双向绑定需要以下几个要点。
- 利用Proxy或Object.defineProperty生成的Observer针对对象/对象的属性进行"劫持",在属性发生变化后通知订阅者
- 解析器Compile解析模板中的Directive(指令),收集指令所依赖的方法和数据,等待数据变化然后进行渲染
- Watcher属于Observer和Compile桥梁,它将接收到的Observer产生的数据变化,并根据Compile提供的指令进行视图渲染,使得数据变化促使视图变化
基于 Object.defineProperty 双向绑定
在Vue实例初始化时会进行 data 的初始化操作,其中会调用 observe 方法并生成一个Observer 监听者对象,在 new Observer 时,即 Observer 的构造函数中会对 data 下的所有属性对象进行监听(递归,直到 !value || typeof value !== 'object' -》 return; ),那么针对每一个属性都会持有自己的一个 Dep 实例(消息管理员)用于存储订阅者。
当 a 属性被属性 b 依赖时(即调用了 a 的 get ),调用 dep.depend(); 方法将 Watcher (订阅者)添加到 dep 中的 subs 数组中存储,同时该订阅者同时持有该 dep 的 id 在 depIds 中(由于 b 可以同时依赖于多个 data 故 depIds 为数组)。
当 this.a = 'new value'; 时,set 方法被触发,并调用 dep.notify(); 通知订阅者更新。
Object.defineProperty的缺陷
- 无法监听数组变化(Vue数组更新检测)
- 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历,显然能劫持一个完整的对象是更好的选择。
基于Proxy的双向绑定
Proxy可以直接监听对象而非属性
Proxy可以直接监听数组的变化
Proxy在ES2015规范中被正式发布,它在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,我们可以这样认为,Proxy是Object.defineProperty的全方位加强版。
Proxy的其他优势
Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的。
Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改。
Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利。
当然,Proxy的劣势就是兼容性问题,而且无法用polyfill磨平,因此Vue的作者才声明需要等到下个大版本(3.0)才能用Proxy重写。
掘金:实现双向绑定Proxy比defineproperty优劣如何?