Vue2響應式系統(tǒng)介紹
前言:
目前工作中大概有 的需求是在用 的技術棧,所謂知其然更要知其所以然,為了更好的使用 、更快的排查問題,最近學習了源碼相關的一些知識,雖然網上總結 的很多很多了,不少自己一個,但也不多自己一個,歡迎一起討論學習,發(fā)現問題歡迎指出。40%Vue2VueVue
一、響應式系統(tǒng)要干什么
回到最簡單的代碼:
data = {
text: 'hello, world'
}
const updateComponent = () => {
console.log('收到', data.text);
}
updateComponent()
data.text = 'hello, liang'
// 運行結果
// 收到 hello, world
響應式系統(tǒng)要做的事情:某個依賴了 數據的函數,當所依賴的 數據改變的時候,該函數要重新執(zhí)行。datadata
我們期望的效果:當上邊 修改的時候, 函數再執(zhí)行一次。data.textupdateComponent
為了實現響應式系統(tǒng),我們需要做兩件事情:
- 知道 中的數據被哪些函數依賴
data data中的數據改變的時候去調用依賴它的函數們
為了實現第 點,我們需要在執(zhí)行函數的時候,將當前函數保存起來,然后在讀取數據的時候將該函數保存到當前數據中。1
第 點就迎刃而解了,當修改數據的時候將保存起來的函數執(zhí)行一次即可。2
在讀取數據和修改數據的時候需要做額外的事情,我們可以通過 重寫對象屬性的 和 函數。Object.defineProperty()getset
二、響應式數據
我們來寫一個函數,重寫屬性的 和 函數。getset
/**
* Define a reactive property on an Object.
*/
export function defineReactive(obj, key, val) {
const property = Object.getOwnPropertyDescriptor(obj, key);
// 讀取用戶可能自己定義了的 get、set
const getter = property && property.get;
const setter = property && property.set;
// val 沒有傳進來話進行手動賦值
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val;
/*********************************************/
// 1.這里需要去保存當前在執(zhí)行的函數
/*********************************************/
return value;
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val;
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
/*********************************************/
// 2.將依賴當前數據依賴的函數執(zhí)行
/*********************************************/
},
});
}
為了調用更方便,我們把第 步和第 步的操作封裝一個 類。12Dep
export default class Dep {
static target; //當前在執(zhí)行的函數
subs; // 依賴的函數
constructor() {
this.subs = []; // 保存所有需要執(zhí)行的函數
}
addSub(sub) {
this.subs.push(sub);
}
depend() {
// 觸發(fā) get 的時候走到這里
if (Dep.target) {
// 委托給 Dep.target 去調用 addSub
Dep.target.addDep(this);
}
}
notify() {
for (let i = 0, l = this.subs.length; i < l; i++) {
this.subs[i].update();
}
}
}
Dep.target = null; // 靜態(tài)變量,全局唯一
我們將當前執(zhí)行的函數保存到 類的 變量上。Deptarget
三、保存當前正在執(zhí)行的函數
為了保存當前的函數,我們還需要寫一個 類,將需要執(zhí)行的函數傳入,保存到 類中的 屬性中,然后交由 類負責執(zhí)行。WatcherWatchergetterWatcher
這樣在 類中, 中保存的就不是當前函數了,而是持有當前函數的 對象。DepsubsWatcher
import Dep from "./dep";
export default class Watcher {
constructor(Fn) {
this.getter = Fn;
this.get();
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get() {
Dep.target = this; // 保存包裝了當前正在執(zhí)行的函數的 Watcher
let value;
try {
// 調用當前傳進來的函數,觸發(fā)對象屬性的 get
value = this.getter.call();
} catch (e) {
throw e;
}
return value;
}
/**
* Add a dependency to this directive.
*/
addDep(dep) {
// 觸發(fā) get 后會走到這里,收集當前依賴
// 當前正在執(zhí)行的函數的 Watcher 保存到 dep 中的 subs 中
dep.addSub(this);
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
// 修改對象屬性值的時候觸發(fā) set,走到這里
update() {
this.run();
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run() {
this.get();
}
}
Watcher 的作用就是將正在執(zhí)行的函數通過 包裝后保存到 中,然后調用傳進來的函數,此時觸發(fā)對象屬性的 函數,會收集當前 。WatcherDep.targetgetWatcher
如果未來修改對象屬性的值,會觸發(fā)對象屬性的 ,接著就會調用之前收集到的 對象,通過 對象的 方法,來調用最初執(zhí)行的函數。setWatcherWatcheruptate
四、響應式數據
回到我們之前沒寫完的 函數,按照上邊的思路,我們來補全一下。defineReactive
import Dep from "./dep";
/**
* Define a reactive property on an Object.
*/
export function defineReactive(obj, key, val) {
const property = Object.getOwnPropertyDescriptor(obj, key);
// 讀取用戶可能自己定義了的 get、set
const getter = property && property.get;
const setter = property && property.set;
// val 沒有傳進來話進行手動賦值
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
/*********************************************/
const dep = new Dep(); // 持有一個 Dep 對象,用來保存所有依賴于該變量的 Watcher
/*********************************************/
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val;
/*********************************************/
// 1.這里需要去保存當前在執(zhí)行的函數
if (Dep.target) {
dep.depend();
}
/*********************************************/
return value;
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val;
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
/*********************************************/
// 2.將依賴當前數據依賴的函數執(zhí)行
dep.notify();
/*********************************************/
},
});
}
五、Observer 對象
我們再寫一個 方法,把對象的全部屬性都變成響應式的。Observer
export class Observer {
constructor(value) {
this.walk(value);
}
/**
* 遍歷對象所有的屬性,調用 defineReactive
* 攔截對象屬性的 get 和 set 方法
*/
walk(obj) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
}
}
我們提供一個 方法來負責創(chuàng)建 對象。observeObserver
export function observe(value) {
let ob = new Observer(value);
return ob;
}
六、測試
將上邊的方法引入到文章最開頭的例子,來執(zhí)行一下:
import { observe } from "./reactive";
import Watcher from "./watcher";
const data = {
text: "hello, world",
};
// 將數據變成響應式的
observe(data);
const updateComponent = () => {
console.log("收到", data.text);
};
// 當前函數由 Watcher 進行執(zhí)行
new Watcher(updateComponent);
data.text = "hello, liang";
此時就會輸出兩次了~
收到 hello, world
收到 hello, liang
說明我們的響應式系統(tǒng)成功了。
七、總結

先從整體理解了響應式系統(tǒng)的整個流程:
每個屬性有一個 數組, 會持有當前執(zhí)行的函數,當讀取屬性的時候觸發(fā) ,將當前 保存到 數組中,當屬性值修改的時候,再通過 數組中的 對象執(zhí)行之前保存的函數。subsWatchergetWatchersubssubsWatcher
到此這篇關于Vue2響應式系統(tǒng)介紹的文章就介紹到這了,更多相關Vue2響應式系統(tǒng)內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
vuex中store存儲store.commit和store.dispatch的區(qū)別及說明
這篇文章主要介紹了vuex中store存儲store.commit和store.dispatch的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09
VUE3?Vite打包后動態(tài)圖片資源不顯示問題解決方法
這篇文章主要給大家介紹了關于VUE3?Vite打包后動態(tài)圖片資源不顯示問題的解決方法,可能是因為在部署后的服務器環(huán)境中對中文文件名的支持不完善,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-09-09
vue element實現將表格單行數據導出為excel格式流程詳解
這篇文章主要介紹了vue element實現將表格單行數據導出為excel格式流程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2022-12-12

