❗️ 注意:この記事が作成されてから既に
日が経過しています。情報の鮮度にご注意ください
本文の背景
Vueのリアクティブシステムの原理について研究し、それを記録するとともに、Vueのリアクティブ機能を自分で実装してみようと思い、本記事を執筆しました。
概要
Vue リアクティブデータの初期化は、initStateのinitDataで行われます。observe関数を使用してvmオブジェクトのdataプロパティを監視し、その後getterとsetterプロパティを設定します。
したがって、少なくとも3つの関数が必要です:プロパティ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 -