概述
在前端的框架中,双向数据绑定是一个非常热门的主题,并且许多的MVVM框架(Knockout, Angular, Vue)都以此为优点。
什么是双向数据绑定呢,首先解释React的单向数据绑定,就是当Model变化,与其绑定的View随Model实时变化。在单向绑定的基础上,对input
等输入控件进行检测,把输入值反应到Model上就是双向数据绑定
这篇文章主要从原理上简单探讨不同的双向绑定的实现,主要有
- 发布订阅模式(Backbone.js)
- 脏检查(Angular.js)
- 数据劫持(Vue.js)
Pub-Sub模式
这篇文章非常详细的解释了该方法,并且具有一个Demo
PubSub模式,又称观察者模式,在设计模式中讨论过,主要通过订阅事件,当事件出发后遍历订阅者发送通知
View -> Model的绑定与其他模式类似,通过JavaScript的API监控keyup
等事件,并且将event.target.value
赋值给Model即可
Model -> View的绑定需要顾及两个方面:
- 数据变化必须使用设置的Set方法,该方法在更新数据后会触发发布者的事件更新View
- 更新绑定的View,可以采用多种方法,这里采用了HTML Attribute
data-bind-uid
,遍历所有具有该属性的DOM,根据种类设置innerHTML
或value
其缺点显然就是必须通过 Set API 来完成数据更新,显然我们更希望通过普通的赋值语句来写代码
源代码如下:
脏检查
脏检查主要是Angular实现的的 Model->View 绑定,最简单的方法就是通过SetInterval()
定时轮询检测数据变动
显然这种方法并不靠谱,Angular.js注册检测了以下事件:
- DOM事件(Click, Keyup)
- XHR响应
- 浏览器Location变化
- Timer事件(SetTimeout和SetInterval)
- 手动执行 $digest() 或 $apply()
当这些事件触发以后,循环检测所有数据对比是否变化,如果没有变化,再循环一次确认是否变化,直到连续两次不变(最多循环10次)
显然这种方法的缺点是,当绑定非常多的View(>2000)时会非常低效,因此称为脏检查
数据劫持
Vue.js通过ES5的Object.defineProperty()
方法,将绑定的Model改写成具有getter
和setter
的属性,当值发生变化时,在setter
里通知变化触发回调
这篇文章通过源码详细分析了Vue的Observer, Watcher, Compile机制,这里简单讨论下其核心
Vue遍历$data
上的所有属性,利用defineProperty
将其重新注册:
对于非数组成员: 深度优先遍历注册所有子对象,对于每个数据,调用函数:
123456789101112131415function defineReactive(data, key, val) {observe(val); // 监听子属性Object.defineProperty(data, key, {enumerable: true, // 可枚举configurable: false, // 不能再defineget: function() {return val;},set: function(newVal) {if (newVal === val) return;val = newVal;dep.notify(); // 通知所有订阅者}});}对于数组成员,我们不能采取这样的方法
- 数组的长度属性
length
无法重定义setter
,因为他是configurable:false
的,因此无法监听直接改变length的方法 - 数组的下标无法直接监听,对于一个
length = 5
的数组a
,可能a[2]
没有定义,也就没有setter
进行监听
- 数组的长度属性
因此在Vue中,劫持了Array.prototype
上所有可以改变数组的方法,包括push
, pop
, shift
, unshift
, splice
, sort
, reverse
,并且定义了$set
和$remove
两个辅助方法。
因此在Vue中不能使用角标和直接修改长度属性