❗️ 注意:离本文创建时间已经过去了
天,请注意时效性
本文由来
研究了下 Vue 的响应式原理, 想记录下来, 顺便将 Vue 的响应式给实现一遍, 因此有了本文.
概述
Vue
响应式数据的初始化, 是在 initState
的 initData
中进行的, 通过 observe
函数观察 vm
对象的 data
属性, 然后设置 getter
和 setter
属性;
因此起码需要三个函数: 设置属性 getter
和 setter
的观察者, 触发 getter
和 setter
的 监听者
, 以及保存属性依赖的 收集框
, 我分别命名为: observe
, watcher
, 和 dep
流程
首先是在 observe
中, 将 data
中的属性, 递归的加上 getter
和 setter
; 之后, 在 watcher
其中的一个属性的时候, watcher
实例触发 getter
拦截器, 然后该属性的拦截器将 watcher
实例添加到该属性的闭包依赖收集器 dep
中, 同时将该闭包依赖收集器, 放入该 watcher
中;
之后, 当修改上述的属性的时候, 触发 observe
中定义的 setter
拦截器, 此时 dep
中的 watcher
将开始工作, 执行该 watcher
的回调;
注意事项
如何保证触发 getter
的时候, 依赖不重复收集?
watcher
触发 getter
的时候, 会把当前属性的依赖收集器添加到 watcher
的 Set 数组中, 进行对比, 如果重复则不再添加:
1 2 3 4 5 6 7
| addDep (dep) { var id = dep.id; if (!this.depIds.has(id)) { this.deps.push(dep); dep.addSubs(this); } }
|
如何在触发 setter
的时候执行回调?
触发 setter
的时候, 会将该属性的中装有 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
| set (newVal) { if (newVal === val) { console.log('值相等, 未变化, 呵呵'); return; } val = newVal; dep.notity(); }
notity () { this.subs.forEach((watcher) => { watcher.update(); }); },
update () { var value = this.get(); if (value !== this.value) { console.log(`恭喜你成功更新了该值, 当前 deps 为: ${this.deps}, depsId 为: ${this.depIds}, 旧值为: ${this.value}, 新值为: ${value}`); this.cb.call(this.data, this.key); } else { console.log('两次相同, 无需更改'); } }
|
具体代码:
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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
| var wId = 0; var dId = 0;
function observe(data) { var propertys = Object.keys(data); var dep = new Dep(); propertys.forEach((property) => { var val = data[property]; Object.defineProperty(data, property, { get() { dep.depend(); return val; }, set(newVal) { if (newVal === val) { console.log('值相等, 未变化, 呵呵'); return; } val = newVal; dep.notity(); }, }); }); }
function Watcher(data, key, cb) { this.data = data; this.key = key; this.cb = cb; this.depIds = new Set(); this.deps = []; this.value = this.get(); }
Watcher.prototype = { update() { var value = this.get(); if (value !== this.value) { console.log( `恭喜你成功更新了该值, 当前 deps 为: ${this.deps}, depsId 为: ${this.depIds}, 旧值为: ${this.value}, 新值为: ${value}` ); this.cb.call(this.data, this.key); } else { console.log('两次相同, 无需更改'); } }, get() { var val; pushTarget(this); val = this.data[this.key]; popTarget(); return val; }, addDep(dep) { var id = dep.id; if (!this.depIds.has(id)) { this.deps.push(dep); dep.addSubs(this); } }, };
function Dep() { this.subs = []; this.id = wId++; } Dep.prototype = { depend() { Dep.target.addDep(this); }, notity() { this.subs.forEach((watcher) => { watcher.update(); }); }, addSubs(watcher) { this.subs.push(watcher); }, };
var targetList = []; function pushTarget(watcher) { Dep.target = watcher; targetList.push(watcher); } function popTarget() { targetList.pop(); Dep.target = targetList[targetList.length - 1]; }
var a = { b: 'c', }; observe(a);
new Watcher(a, 'b', function (data, key) { console.log('watcher 回调成功执行!'); });
|
代码执行顺序示意图:
- EOF -