数据响应式更新原理
现代的前端框架都引入了数据的响应式更新系统,modal
层更新的数据能实时的影响到view
层,这为我们的开发带来了极大的便利。这些的响应原理是什么呢。
ES6之前的响应式
JavaScript 中对象的每个属性都拥有一个属性描述对象,用来定义该属性的行为。Object.defineProperty 是 ES5.1 规范中提供的方法,用来修改对象属性的属性描述对象,文档可参见 MDN-Object.defineProperty()。通过 Object.defineProperty 函数,我们可以通过定义对象属性的存取器(getter/setter)来劫持数据的读取,实现数据变动时的通知功能。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| let o = { a: 1 } let value = o.a Object.defineProperty(o, 'a', { enumerable: true, configurable: true, get () { console.log('a 属性被访问') return value }, set (newValue) { console.log('a 属性被修改了,新值为', newValue, '旧值为', value) value = newValue } })
|
通过对对象属性的存储器(getter/setter)进行劫持,使我们能够在对象的属性更改后得到通知,这就是vue的响应式更新的原理。由于该方法不能够被polyfill,所以不支持ie9一下浏览器
数据响应变化
Object.defineProperty
方法一次只能定义一个键值的属性描述对象,因此我们可以很容易的写出一个监测函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| let o = { a: 1 } function watch (obj, key, callback) { let value = obj[key] Object.defineProperty(obj, key, { enumerable: true, configurable: true, get () { return value }, set (newValue) { let oldValue = value value = newValue callback(newValue, oldValue) } }) } watch(o, 'a', (newValue, oldValue) => { console.log('属性被修改了,新值为:', newValue, ',旧值为:', oldValue) }) o.a = 2 o.a = 3
|
递归遍历整个对象
为了让整个对象响应化,我们需要遍历对象中的所有键并为其应用 Object.defineProperty
方法。对于键值为对象的情况,递归进去处理。同时,为了让 『使数据响应化』和『添加回调函数』两个操作解耦,我们引入 Dep
和 Watcher
类,使用订阅/发布模式向响应式数据注册回调函数。
1 2 3 4 5 6 7
| 变动时通知 data --------------> Dep类 收集回调 | ^ 发布 | | | |订阅 ^ | Watcher
|
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| class Dep { constructor () { this.subs = [] } addSub (sub) { this.subs.push(sub) }, notify (newValue, oldValue) { this.subs.forEach(sub => { sub.update(newValue, oldValue) }) } } const globalDep = new Dep() class Watcher { constructor (callback) { this.callback = callback } update (newValue, oldValue) { this.callback(newValue, oldValue) } } function observify(value) { if (!isObject(value)) { return false } Object.keys(value).forEach(key => { defineReactive(value, key, value[key]) }) } function isObject(value) { return typeof value === 'object' && value !== null } function defineReactive (obj, key, value) { observify(value) Object.defineProperty(obj, key, { enmuerable: true, configurable: true, get () { return value }, set (newValue) { if (newValue === value) { return } else { let oldValue = value value = newValue observify(newValue) globalDep.notify(newValue, oldValue) } } }) } let o = { a: 1, c: { d: 1 } } observify(o) globalDep.addSub(new Watcher((newValue, oldValue) => { console.log('发生改变!新值:', newValue, ",旧值:", oldValue) })) o.a = 2 o.c.d = 4 globalDep.addSub(new Watcher((newValue, oldValue) => { console.log('新订阅') })) o.a = 3
|