#vue は、多くの便利な機能が組み込まれた使いやすいフレームワークで、最も特徴的な機能は下部に隠されたレスポンシブ システムです。コンポーネントの状態はすべて、応答性の高い JavaScript オブジェクトです。それらを変更するとビューが更新され、状態管理がより簡単かつ直感的になります。では、Vue 応答システムはどのように実装されているのでしょうか?この記事も Vue のソースコードを読んだ上での理解と模倣実装をベースにしていますので、著者の考えに従って Vue を浅いところから深いところまで探っていきましょう! [関連する推奨事項: vuejs ビデオ チュートリアル ]
この記事の Vue ソース コード バージョン: 2.6.14. 理解を容易にするために、コードは簡略化されています。
通常の JavaScript オブジェクトをデータ オプションとして Vue インスタンスに渡すと、Vue はオブジェクトのすべてのプロパティを走査します。そして、Object.defineProperty を使用してこれらすべてのプロパティをゲッター/セッターに変換し、ゲッター/セッターを実行します。
Vue の応答システムを一文で要約すると、次のようになります。 Observer モード Object.defineProperty が getter/setter をインターセプト
Object.defineProperty() メソッドは、オブジェクトの新しいプロパティを直接定義するか、オブジェクトの既存のプロパティを変更します。このオブジェクトを返します。
obj.xxx の実行時に get をトリガーし、
obj.xxx = xxx の実行時に set をトリガーします。応答性の鍵となります。
Object.defineProperty は、シム化できない (ポリフィルを介して実装できない) ES5 の機能です。そのため、Vue は IE8 以前のブラウザをサポートしません。
Object.defineProperty に基づいて簡単なシステムを実装しましょう。 「前菜」としてのレスポンシブ更新システム
let data = {}; // 使用一个中间变量保存 value let value = "hello"; // 用一个集合保存数据的响应更新函数 let fnSet = new Set(); // 在 data 上定义 text 属性 Object.defineProperty(data, "text", { enumerable: true, configurable: true, set(newValue) { value = newValue; // 数据变化 fnSet.forEach((fn) => fn()); }, get() { fnSet.add(fn); return value; }, }); // 将 data.text 渲染到页面上 function fn() { document.body.innerText = data.text; } // 执行函数,触发读取 get fn(); // 一秒后改变数据,触发 set 更新 setTimeout(() => { data.text = "world"; }, 1000);
オブザーバー パターンとは何ですか?
これは、オブジェクト イベントの発生時にオブジェクトを「監視」する他の複数のオブジェクトに通知できるサブスクリプション メカニズムを定義できる動作設計パターンです。 注目に値するステータスを持つオブジェクトは通常、ターゲットと呼ばれます。自身のステータスが変化したときに他のオブジェクトに通知する必要があるため、パブリッシャーとも呼ばれます。 ## #。パブリッシャーの状態の変化を追跡したい他のすべてのオブジェクトは、subscribers と呼ばれます。さらに、パブリッシャーはインターフェースを介してのみすべてのサブスクライバーと直接対話し、すべてのサブスクライバーが 同じインターフェースを持っている必要があります 。
たとえば?:
あなた (つまり、アプリケーションの購読者) は、特定の書店の週刊誌に興味があり、上司 (つまり、アプリケーション ) の発行者は電話番号を残して、新しい週刊誌が入手可能になり次第、上司に電話するよう依頼しました。この週刊誌に興味を持った他の人も、上司に電話番号を残しました。新しい週刊誌が届くと、上司は読者にそれを受け取るように一人ひとりに電話をかけます。
リーダーが誤って電話番号の代わりに QQ 番号を残した場合、古いバージョンでは電話をかけることができず、リーダーは通知を受信できません。これは上で述べたとおりであり、同じインターフェイスを持つ必要があります。
オブザーバー パターンを理解した後、応答性の高いシステムの設計を開始しました。
抽象オブザーバー (サブスクライバー) クラス Watcher 在上面的例子中,数据和响应更新函数是通过硬编码强耦合在一起的。而实际开发过程中,更新函数不一定叫 抽象被观察者(发布者)类Dep 我们再想一想,实际开发过程中,data 中肯定不止一个数据,而且每个数据,都有不同的订阅者,所以说我们还需要抽象一个被观察者(发布者) 抽象 Observer 现在, 答案就是 下面我们就来完善将数据转换为getter/setter的处理。 上面基础的响应式系统实现中,我们只定义了一个响应式数据,当 data 中有其他property时我们就处理不了了。所以,我们需要抽象一个 为了简化,我们只处理data中单层数据。 至此,响应式系统就大功告成了!! 测试 我们通过下面代码测试一下: 在浏览器中运行这段代码,和我们期望的一样,两个 我们尝试修改 我们只修改 都完美通过测试!!? 总结 本篇文章属于入门篇,并非源码实现,在源码的基础上简化了很多内容,能够便于理解 本文的源码,以及作者学习 Vue 源码完整的逐行注释源码地址:github.com/yue1123/vue…fn
,更有可能是一个匿名函数。所以我们需要抽像一个观察者(订阅者)类Watcher
来保存并执行更新函数,同时向外提供一个update
更新接口。// Watcher 观察者可能有 n 个,我们为了区分它们,保证唯一性,增加一个 uid
let watcherId = 0;
// 当前活跃的 Watcher
let activeWatcher = null;
class Watcher {
constructor(cb) {
this.uid = watcherId++;
// 更新函数
this.cb = cb;
// 保存 watcher 订阅的所有数据
this.deps = [];
// 初始化时执行更新函数
this.get();
}
// 求值函数
get() {
// 调用更新函数时,将 activeWatcher 指向当前 watcher
activeWatcher = this;
this.cb();
// 调用完重置
activeWatcher = null;
}
// 数据更新时,调用该函数重新求值
update() {
this.get();
}
}
Dep
类来保存数据对应的观察者(Watcher
),以及数据变化时通知观察者更新。class Dep {
constructor() {
// 保存所有该依赖项的订阅者
this.subs = [];
}
addSubs() {
// 将 activeWatcher 作为订阅者,放到 subs 中
// 防止重复订阅
if(this.subs.indexOf(activeWatcher) === -1){
this.subs.push(activeWatcher);
}
}
notify() {
// 先保存旧的依赖,便于下面遍历通知更新
const deps = this.subs.slice()
// 每次更新前,清除上一次收集的依赖,下次执行时,重新收集
this.subs.length = 0;
deps.forEach((watcher) => {
watcher.update();
});
}
}
Watcher
和Dep
只是两个独立的模块,我们怎么把它们关联起来呢?Object.defineProperty
,在数据被读取,触发get
方法,Dep 将当前触发 get 的 Watcher 当做订阅者放到 subs中,Watcher
就与 Dep
建立关系;在数据被修改,触发set
方法,Dep
就遍历 subs 中的订阅者,通知Watcher
更新。Observer
类来完成对 data数据的遍历,并调用defineReactive
转换为 getter/setter,最终完成响应式绑定。class Observer {
constructor(value) {
this.value = value;
this.walk(value);
}
// 遍历 keys,转换为 getter/setter
walk(obj) {
const keys = Object.keys(obj);
for (let i = 0; i <p>这里我们通过参数 value 的闭包,来保存最新的数据,避免新增其他变量</p><pre class="brush:php;toolbar:false">function defineReactive(target, key, value) {
// 每一个数据都是一个被观察者
const dep = new Dep();
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
// 执行 data.xxx 时 get 触发,进行依赖收集,watcher 订阅 dep
get() {
if (activeWatcher) {
// 订阅
dep.addSubs(activeWatcher);
}
return value;
},
// 执行 data.xxx = xxx 时 set 触发,遍历订阅了该 dep 的 watchers,
// 调用 watcher.updata 更新
set(newValue) {
// 如果前后值相等,没必要跟新
if (value === newVal) {
return;
}
value = newValue;
// 派发更新
dep.notify();
},
});
}
let data = {
name: "张三",
age: 18,
address: "成都",
};
// 模拟 render
const render1 = () => {
console.warn("-------------watcher1--------------");
console.log("The name value is", data.name);
console.log("The age value is", data.age);
console.log("The address value is", data.address);
};
const render2 = () => {
console.warn("-------------watcher2--------------");
console.log("The name value is", data.name);
console.log("The age value is", data.age);
};
// 先将 data 转换成响应式
new Observer(data);
// 实例观察者
new Watcher(render1);
new Watcher(render2);
render
都执行了,并且在控制台上打印了结果。data.name = '李四 23333333'
,测试两个 render
都会重新执行:data.address = '北京'
,测试一下是否只有render 1
回调都会重新执行:Vue
响应式原理的核心就是Observer
、Dep
、Watcher
,三者共同构成 MVVM 中的 VMObserver
中进行数据响应式处理以及最终的Watcher
和Dep
关系绑定,在数据被读的时候,触发get
方法,将 Watcher
收集到 Dep
中作为依赖;在数据被修改的时候,触发set
方法,Dep
就遍历 subs 中的订阅者,通知Watcher
更新。Observer
、Dep
、Watcher
三者的作用和关系。
以上がVue のレスポンシブ実装の原理について説明する記事の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。