探究下主流JS框架对应的数据绑定的实现方式

数据绑定的方式,大致分为以下几种

发布者-订阅者模式backbone.js

脏值检测angular.js

数据劫持vue.js

Vue

Object.defineProperty()方法会在一个对象上定义一个新属性,或修改已经存在的属性,并返回这个对象

vue通过Object.defineProperty()来劫持各个属性的gettersetter,在数据变更时候,发布消息订阅者,触发相应的回调。

Object.getOwnPropertyDescription()返回一个对象上的自有属性(非原型链上的属性)对应的属性描述符

遍历对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function touch (obj) {
if (typeof obj === 'object') {
if (Array.isArray(obj)) {
for (let i = 0, l = obj.length; i < l; i++) {
touch(obj[i])
}
} else {
let keys = Object.keys(obj)
for (let key of keys) {
touch(obj[key])
}
}
}
}

遇到普通数据属性,直接处理该属性。遇到对象,遍历属性之后递归进去处理属性。

设置对象上的属性的gettersetter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function defineReactive(obj, key, val) {
var property = Object.getOwnProperty(obj, key)
if (property && property.configurable === false) {
return
}

var getter = property && property.get
var setter = property && property.set

Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function () {
var value = getter ? getter.call(obj) : val
dep.push(value)
return value
},
set: function (newVal) {
var value = getter ? getter.call(obj) : val
if (newVal === value) {
return
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
}
})
}

对于设置对象的属性的getter/setter,能够监听到对象的属性值的变化。对于数组,通过修改length和通过下标修改数组的形式,无法监听到数组的变化。如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//html
<div id="app">
<div v-for="item in list">{{item}}</div>
</div>

//js
var app = new Vue({
el: '#app',
data: {
list: [1, 2, 3, 4]
},
mounted () {
this.list.length = 3 // not work
this.list[0] = 6 // not work
}
})