Data binding is a general technique that binds data sources from the provider and consumer together and synchronizes them.
在前端组件化框架中,基本都有数据绑定这一特性,数据绑定是将生产者的数据源绑定到消费者,并负责数据同步。本文将探究数据绑定的不同实现方式。🤓
目前主要有以下几种实现方案:
- 脏检查(dirty check)
- 存取器方法(accessor method):
Object.defineProperty
ES5 Proxy
: ES6 支持较差【Proxies are one of the few non polyfillable additions.(babel不支持转换)】Object.observe
: ES7,已经移出草案,不支持object.watch
: 目前只有基于gecko的浏览器如火狐支持,官方建议仅供调试用
其中前两种方式是主流,angular、Polymer 使用的就是脏检查,Vue 使用的就是存取器方法。
脏检查(dirty check)
通过一个例子来阐述脏检查的原理:
定义一个 provider
对象作为生产者,DOM 节点 consumer
作为消费者,通过 observe
监听,生产者数据的变更会反映到消费者上,这样也就简单的实现了数据绑定,即生产者数据绑定到了消费者上。
而其中监听函数 observe
只是将对某一个属性的监听回调 handler
保存起来,方便在该属性值发生变化时,执行该回调。
接下来就是监听变化过程了,脏检查之所以称之为脏,是因为其不是直接监听属性是否发生了变更,而是通过一个定时器轮询检查,不断的遍历检查对象的新值和旧值,判断是否发生了变化,若是,则调用会回调函数 handler
将生产者的数据同步到消费者上。
所以对于 digest
方法就需要循环进行调用,譬如使用 setInterval
或者 requestAnimationFrame
方法,显然这个过程会对性能产生影响,当监听的对象越来越多时尤其明显。
在 Polymer 1.0 版本中的数据绑定也是使用脏检查的解决方案,在其 observe-js-behavior.html
文件中我们就发现了通过定时器轮询脏检查的代码:
而 angular 就对此也进行了优化,其并不使用定时轮询脏检查,而是对常用的 DOM 事件, XHR 事件等进行封装,在里面触发进入脏检查。
脏检查应该是实现对象监听比较成熟和完整的解决方案。可以参考以下两个项目:
而对于前者,也是 Polymer 框架中使用的监听方案,
存取器方法(accessor method)
这种方式是通过重写对象(Object
类型)属性的 set
和 get
方法,在 setter
方法中执行相应的监听回调(callback)来实现的。
对对象的监听需要考虑 Object
类型和 Array
类型这两种类型
Step 1
首先我们考虑情况一,对象中不包含 Array
类型
📌注意
for...in
和Object.keys
的区别,参考链接for...in
循环中,变量定义不能使用var
,需要使用let
或者用闭包,如下1234567891011121314151617181920...for (let prop in obj) {(function() {var value = obj[prop],_path = path.slice();_path.push(prop); // 记录路径Object.defineProperty(obj, prop, {set: function(newVle) {callback(_path, newVle, value);value = newVle;},get: function() {return value;}});// 递归observe(value, _path);})()}...
测试代码:
💣 局限性
通过 Object.defineProperty
实现对对象的监听,我们是在 set
方法中做文章,那么监听的情况也就只限于对象属性的修改(modify),如果对对象属性的增删(add/delete),那么就无能为力了。
Step 2
接着我们考虑情况二,对象中包含 Object
类型和 Array
类型
涉及到数组类型,解决的方法是,重新封装一下数组的操作方法 ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
,在其中调用相应的监听回调
测试代码:
📌注意
- 由于数组不同操作方法的参数个数不同,所以在重新定义时需要对此进行判断处理,此处暂不处理,监听回调也只返回 path 路径
- 上述对数组的处理还存在很多问题,譬如测试代码中,新添加的
5
这个值并没有进行监听,如果此时对obj.c[2]
进行修改,那么发现并没有回调监听函数;所以此处只提供一个思路。
参考链接
Writing a JavaScript Framework - Introduction to Data Binding, beyond Dirty Checking