Vue2響應(yīng)式系統(tǒng)介紹
前言:
目前工作中大概有 的需求是在用 的技術(shù)棧,所謂知其然更要知其所以然,為了更好的使用 、更快的排查問題,最近學(xué)習(xí)了源碼相關(guān)的一些知識(shí),雖然網(wǎng)上總結(jié) 的很多很多了,不少自己一個(gè),但也不多自己一個(gè),歡迎一起討論學(xué)習(xí),發(fā)現(xiàn)問題歡迎指出。40%
Vue2
Vue
Vue
一、響應(yīng)式系統(tǒng)要干什么
回到最簡(jiǎn)單的代碼:
data = { text: 'hello, world' } const updateComponent = () => { console.log('收到', data.text); } updateComponent() data.text = 'hello, liang' // 運(yùn)行結(jié)果 // 收到 hello, world
響應(yīng)式系統(tǒng)要做的事情:某個(gè)依賴了 數(shù)據(jù)的函數(shù),當(dāng)所依賴的 數(shù)據(jù)改變的時(shí)候,該函數(shù)要重新執(zhí)行。data
data
我們期望的效果:當(dāng)上邊 修改的時(shí)候, 函數(shù)再執(zhí)行一次。data.text
updateComponent
為了實(shí)現(xiàn)響應(yīng)式系統(tǒng),我們需要做兩件事情:
- 知道 中的數(shù)據(jù)被哪些函數(shù)依賴
data
data
中的數(shù)據(jù)改變的時(shí)候去調(diào)用依賴它的函數(shù)們
為了實(shí)現(xiàn)第 點(diǎn),我們需要在執(zhí)行函數(shù)的時(shí)候,將當(dāng)前函數(shù)保存起來,然后在讀取數(shù)據(jù)的時(shí)候?qū)⒃摵瘮?shù)保存到當(dāng)前數(shù)據(jù)中。1
第 點(diǎn)就迎刃而解了,當(dāng)修改數(shù)據(jù)的時(shí)候?qū)⒈4嫫饋淼暮瘮?shù)執(zhí)行一次即可。2
在讀取數(shù)據(jù)和修改數(shù)據(jù)的時(shí)候需要做額外的事情,我們可以通過 重寫對(duì)象屬性的 和 函數(shù)。Object.defineProperty()
get
set
二、響應(yīng)式數(shù)據(jù)
我們來寫一個(gè)函數(shù),重寫屬性的 和 函數(shù)。get
set
/** * 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 沒有傳進(jìn)來話進(jìn)行手動(dòng)賦值 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.這里需要去保存當(dāng)前在執(zhí)行的函數(shù) /*********************************************/ return value; }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val; if (setter) { setter.call(obj, newVal); } else { val = newVal; } /*********************************************/ // 2.將依賴當(dāng)前數(shù)據(jù)依賴的函數(shù)執(zhí)行 /*********************************************/ }, }); }
為了調(diào)用更方便,我們把第 步和第 步的操作封裝一個(gè) 類。1
2
Dep
export default class Dep { static target; //當(dāng)前在執(zhí)行的函數(shù) subs; // 依賴的函數(shù) constructor() { this.subs = []; // 保存所有需要執(zhí)行的函數(shù) } addSub(sub) { this.subs.push(sub); } depend() { // 觸發(fā) get 的時(shí)候走到這里 if (Dep.target) { // 委托給 Dep.target 去調(diào)用 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)變量,全局唯一
我們將當(dāng)前執(zhí)行的函數(shù)保存到 類的 變量上。Dep
target
三、保存當(dāng)前正在執(zhí)行的函數(shù)
為了保存當(dāng)前的函數(shù),我們還需要寫一個(gè) 類,將需要執(zhí)行的函數(shù)傳入,保存到 類中的 屬性中,然后交由 類負(fù)責(zé)執(zhí)行。Watcher
Watcher
getter
Watcher
這樣在 類中, 中保存的就不是當(dāng)前函數(shù)了,而是持有當(dāng)前函數(shù)的 對(duì)象。Dep
subs
Watcher
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; // 保存包裝了當(dāng)前正在執(zhí)行的函數(shù)的 Watcher let value; try { // 調(diào)用當(dāng)前傳進(jìn)來的函數(shù),觸發(fā)對(duì)象屬性的 get value = this.getter.call(); } catch (e) { throw e; } return value; } /** * Add a dependency to this directive. */ addDep(dep) { // 觸發(fā) get 后會(huì)走到這里,收集當(dāng)前依賴 // 當(dāng)前正在執(zhí)行的函數(shù)的 Watcher 保存到 dep 中的 subs 中 dep.addSub(this); } /** * Subscriber interface. * Will be called when a dependency changes. */ // 修改對(duì)象屬性值的時(shí)候觸發(fā) set,走到這里 update() { this.run(); } /** * Scheduler job interface. * Will be called by the scheduler. */ run() { this.get(); } }
Watcher
的作用就是將正在執(zhí)行的函數(shù)通過 包裝后保存到 中,然后調(diào)用傳進(jìn)來的函數(shù),此時(shí)觸發(fā)對(duì)象屬性的 函數(shù),會(huì)收集當(dāng)前 。Watcher
Dep.target
get
Watcher
如果未來修改對(duì)象屬性的值,會(huì)觸發(fā)對(duì)象屬性的 ,接著就會(huì)調(diào)用之前收集到的 對(duì)象,通過 對(duì)象的 方法,來調(diào)用最初執(zhí)行的函數(shù)。set
Watcher
Watcher
uptate
四、響應(yīng)式數(shù)據(jù)
回到我們之前沒寫完的 函數(shù),按照上邊的思路,我們來補(bǔ)全一下。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 沒有傳進(jìn)來話進(jìn)行手動(dòng)賦值 if ((!getter || setter) && arguments.length === 2) { val = obj[key]; } /*********************************************/ const dep = new Dep(); // 持有一個(gè) Dep 對(duì)象,用來保存所有依賴于該變量的 Watcher /*********************************************/ Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val; /*********************************************/ // 1.這里需要去保存當(dāng)前在執(zhí)行的函數(shù) 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.將依賴當(dāng)前數(shù)據(jù)依賴的函數(shù)執(zhí)行 dep.notify(); /*********************************************/ }, }); }
五、Observer 對(duì)象
我們?cè)賹懸粋€(gè) 方法,把對(duì)象的全部屬性都變成響應(yīng)式的。Observer
export class Observer { constructor(value) { this.walk(value); } /** * 遍歷對(duì)象所有的屬性,調(diào)用 defineReactive * 攔截對(duì)象屬性的 get 和 set 方法 */ walk(obj) { const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } } }
我們提供一個(gè) 方法來負(fù)責(zé)創(chuàng)建 對(duì)象。observe
Observer
export function observe(value) { let ob = new Observer(value); return ob; }
六、測(cè)試
將上邊的方法引入到文章最開頭的例子,來執(zhí)行一下:
import { observe } from "./reactive"; import Watcher from "./watcher"; const data = { text: "hello, world", }; // 將數(shù)據(jù)變成響應(yīng)式的 observe(data); const updateComponent = () => { console.log("收到", data.text); }; // 當(dāng)前函數(shù)由 Watcher 進(jìn)行執(zhí)行 new Watcher(updateComponent); data.text = "hello, liang";
此時(shí)就會(huì)輸出兩次了~
收到 hello, world
收到 hello, liang
說明我們的響應(yīng)式系統(tǒng)成功了。
七、總結(jié)
先從整體理解了響應(yīng)式系統(tǒng)的整個(gè)流程:
每個(gè)屬性有一個(gè) 數(shù)組, 會(huì)持有當(dāng)前執(zhí)行的函數(shù),當(dāng)讀取屬性的時(shí)候觸發(fā) ,將當(dāng)前 保存到 數(shù)組中,當(dāng)屬性值修改的時(shí)候,再通過 數(shù)組中的 對(duì)象執(zhí)行之前保存的函數(shù)。subs
Watcher
get
Watcher
subs
subs
Watcher
到此這篇關(guān)于Vue2響應(yīng)式系統(tǒng)介紹的文章就介紹到這了,更多相關(guān)Vue2響應(yīng)式系統(tǒng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vant中的picker選擇器自定義選項(xiàng)內(nèi)容
這篇文章主要介紹了vant中的picker選擇器自定義選項(xiàng)內(nèi)容,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12Vue?warn:Property?"state"?was?accessed?during
這篇文章主要為大家介紹了Vue?warn:Property?"state"?was?accessed?during?render的報(bào)錯(cuò)解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06vue2中seo時(shí)使用vue-meta-info的方法
這篇文章主要介紹了vue2中seo時(shí)使用vue-meta-info,本文通過實(shí)例代碼給大家詳細(xì)講解,文末給大家補(bǔ)充介紹了vue seo管理 vue-meta-info 動(dòng)態(tài)設(shè)置meta和title的相關(guān)知識(shí),需要的朋友可以參考下2022-10-10vue elementUI table表格數(shù)據(jù) 滾動(dòng)懶加載的實(shí)現(xiàn)方法
這篇文章主要介紹了vue elementUI table表格數(shù)據(jù)滾動(dòng)懶加載的實(shí)現(xiàn)方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04element-ui中dialog彈窗關(guān)閉按鈕失效的解決
這篇文章主要介紹了element-ui中dialog彈窗關(guān)閉按鈕失效的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09圖文詳解vue中proto文件的函數(shù)調(diào)用
這篇文章主要給大家介紹了vue中proto文件函數(shù)調(diào)用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用vue具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-08-08