关于双向数据绑定
当我们在前端开发中采用MV*的模式时,M-model,指的是模型,也就是数据,V-view,指的是视图,也就是页面展现的部分。通常,我们需要编写代码,将从服务器获取的数据进行“渲染”,展现到视图上。每当数据有变更时,我们会再次进行渲染,从而更新视图,使得视图与数据保持一致。也就是:
而另一方面,页面也会通过用户的交互,产生状态、数据的变化,这个时候,我们则编写代码,将视图对数据的更新同步到数据,以致于同步到后台服务器。也就是:
不同的前端 MV* 框架对于这种 Model 和 View 间的数据同步有不同的处理。在 Backbone 中,Model 到 View 的数据传递,可以在 View 中监听 Model 的 change 事件,每当 Model 更新,View 中重新执行 render。而 View 到 Model 的数据传递,可以监听 View 对应的 DOM 元素的各种事件,在检测到 View 状态变更后,将变更的数据发送到 Model。相较于 Backbone,AngularJS 所代表的 MVVM 框架则更进一步,从框架层面支持这种数据同步机制,而且是双向数据绑定:
不过在不同的 MVVM 框架中,实现双向数据绑定的技术有所不同。
AngularJS 采用“脏值检测”的方式,数据发生变更后,对于所有的数据和视图的绑定关系进行一次检测,识别是否有数据发生了改变,有变化进行处理,可能进一步引发其他数据的改变,所以这个过程可能会循环几次,一直到不再有数据变化发生后,将变更的数据发送到视图,更新页面展现。如果是手动对 ViewModel 的数据进行变更,为确保变更同步到视图,需要手动触发一次“脏值检测”。
VueJS 则使用 ES5 提供的 Object.defineProperty() 方法,监控对数据的操作,从而可以自动触发数据同步。并且,由于是在不同的数据上触发同步,可以精确的将变更发送给绑定的视图,而不是对所有的数据都执行一次检测。
关于Object.defineProperty
这个函数接受三个参数,一个参数是obj,表示要定义属性的对象,一个参数是prop,是要定义或者更改的属性名字,另外是descriptor,描述符,来定义属性的具体描述。
Object.defineProperty(obj, prop, descriptor)
下面的是实例代码,obj是一个没有属性的空对象,然后”key”是属性名,{}大括号里面定义了要给属性赋值的情况,value代表属性的值,proto代表继承属性的性质,这里面还有其他的选项。比如configurable,enumerable,writable等默认是false的。
|
|
我们通过控制台的结果来感受一下writable为false的作用。我们发现,就算对”key”属性重新赋值了,它的属性仍然保持不变。
控制台结果
descriptors(描述符)分成两种,一种是data descriptors,另外一种是 accessor descriptors.两种的descriptors有两个必选项,configurable和enumerable
configurable
true if and only if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object.Defaults to false
.
代表这个属性的descriptor也就是描述是可以更改的,这个熟悉也能从对象上面删除,默认false,也就是不能更改跟属性有关的任意值,如果我重新对这个属性进行定义的话,会提示出错,同时也不能删除。
configurable
enumerable
true if and only if this property shows up during enumeration of the properties on the corresponding object.Defaults to false
.
代表这个属性能够通过for in或者Object.keys
来遍历。默认为false
关于enumerable的属性
A data descriptor有两个可选项.
value
The value associated with the property. Can be any valid JavaScript value (number, object, function, etc).Defaults to undefined
.
这个选项为属性赋值,可以是任意的JavaScript值,默认为undefined
writable
true if and only if the value associated with the property may be changed with an assignment operator.Defaults to false
.
writable表示能不能够重写属性值,默认为false
accessor descriptor也有两个关键的属性。
get
A function which serves as a getter for the property, or undefined
if there is no getter. The function return will be used as the value of property.Defaults to undefined
.
set
定义了一个函数,作为属性的getter,如果没有getter就为undefined 默认为undefined
set
A function which serves as a setter for the property, or undefined
if there is no setter. The function will receive as only argument the new value being assigned to the property.Defaults to undefined
.
同get
这里面有一点是,可能会从原型链上面继承相应的属性,如果想避免这种情况,可以写get。所以可以用proto: null
下面是一个可爱的例子
|
|
执行结果
第一段代表定义了一个data descriptor,第二段代表定义了accessor descriptor,通get定义了取值操作,第三段代码告诉我们这两种不能混用。
视图和数据变化绑定
而vue.js主要利用了accessor descriptors的set和get来更新视图,这里看到的这个例子挺好,是一个简单的绑定。
对于一个html页面
|
|
设置一个数据的属性的getter和setter
|
|
然后就能愉快地绑定数据交互了。
|
|
vue.js的数据变动
但是,这个例子只是数据和dom节点的绑定,而vue.js更为复杂一点,它在网页dom和accessor之间会有两层,一层是Wacher,一层是Directive,比如以下代码。
|
|
把一个普通对象(a={b:1})传给 Vue 实例作为它的 data 选项,Vue.js 将遍历它的属性,用Object.defineProperty 将它们转为 getter/setter,如图绿色的部分所示。
每次用户更改data里的数据的时候,比如a.b =1,setter就会重新通知Watcher进行变动,Watcher再通知Directive对dom节点进行更改。