02 vue發布訂閱模式原理(Vue的雙向數據綁定原理)

时间:2024-04-29 23:45:04 编辑: 来源:

ue3 采用了新的 Proxy 實現數據讀取和設置攔截,不僅彌補了之前 Vue2 中 Object.defineProperty 的缺陷,同時也帶來了性能上的提升。

今天,我們就來盤一盤它,看看 Vue3 中響應式是如何實現的。

The Proxy object enables you to create a proxy for another object, which can intercept and redefine fundamental operations for that object. MDN

Proxy - 代理,顧名思義,就是在要訪問的對象之前增加一個中間層,這樣就不直接訪問對象,而是通過中間層做一個中轉,通過操作代理對象,來實現修改目標對象。

Vue3 中響應式核心方法就是 reactive 和 effect , 其中 reactive 方法是負責將數據變成響應式, effect 方法的作用是根據數據變化去更新視圖或調用函數,與 react 中的 useEffect 有點類似~

其大概用法如下:

默認會執行一次,打印 Hello , 之后更改了 data.name 的值后,會在觸發執行一次,打印 World 。

我們先看看 reactive 方法的實現~

reactive.js

首先應該明確,我們應該導出一個 reactive 方法,該方法有一個參數 target ,目的就是將 target 變成響應式對象,因此返回值就是一個響應式對象。

reactive 方法基本結構就是如此,給定一個對象,返回一個響應式對象。

其中 isObject 方法用于判斷是否是對象,不是對象不需要代理,直接返回即可。

reactive 方法的重點是 Proxy 的第二個參數 handler ,它承載監控對象變化,依賴收集,視圖更新等各項重大責任,我們重點來研究這個對象。

handler.js

在 Vue3 中 Proxy 的 handler 主要設置了 get , set , deleteProperty , has , ownKeys 這些屬性,即攔截了對象的讀取,設置,刪除, in 以及 Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法。

這里我們偷個懶,暫時就考慮 set 和 get 操作。

handler.get()

get 獲取屬性比較簡單,我們先來看看這個,這里我們用一個方法創建 getHanlder 。

這里推薦使用了 Reflect.get 而并非 target[key] 。

可以發現, Vue3 是在取值的時候才去遞歸遍歷屬性的,而非 Vue2 中一開始就遞歸 data 給每個屬性添加 Watcher ,這也是 Vue3 性能提升之一。

handler.set()

同理 set 操作,我們也是用一個方法創建 setHandler 。

Reflect.set 會返回一個 Boolean 值,用于判斷屬性是否設置成功。

完事后將 handler 導出,然后在 reactive 中引入即可。

測試幾組對象貌似沒啥問題,其實是有一個坑,這個坑也跟數組有關。

如上例子,如果我們選擇代理數組,在 setHandler 中打印其 key 和 value 的話會得到 3 4 , length 4 這兩組值:

如果不作處理,那么會導致如果更新視圖的話,則會觸發兩次,這肯定是不允許的,因此,我們需要將區分新增和修改這兩種操作。

Vue3 中是通過判斷 target 是否存在該屬性來區分是新增還是修改操作,需要借助一個工具方法 —— hasOwnProperty 。

這里我們將上述的 createSetter 方法修改如下:

如此一來,我們調 push 方法的時候,就只會觸發一次更新了,非常巧妙的避免了無意義的更新操作。

effect.js

光上述構造響應式對象并不能完成響應式的操作,我們還需要一個非常重要的方法 effect ,它會在初始化執行的時候存儲跟其有關的數據依賴,當依賴數據發生變化的時候,則會再次觸發 effect 傳遞的函數。

其基本雛形如下,入參是一個函數,還有個可選參數 options 方便后面計算屬性等使用,暫時不考慮:

createReactiveEffect 就是為了將 fn 變成響應式函數,監控數據變化,執行 fn 函數,因此該函數是一個高階函數。

createReactiveEffect 將原來的 fn 轉變成一個 reactvieEffect , 并將當前的 effect 掛到全局的 activeEffect 上,目的是為了一會與當前所依賴的屬性做好對應關系。

我們必須要將依賴屬性構造成 { prop : [effect,effect] } 這種結構,才能保證依賴屬性變化的時候,依次去觸發與之相關的 effect ,因此,需要在 get 屬性的時候,做屬性的依賴收集,將屬性與 effect 關聯起來。

依賴收集 —— track

在獲取對象的屬性時,會觸發 getHandler ,再次做屬性的依賴收集,即 Vue2 中的發布訂閱。

在 setHandler 中獲取屬性的時候,做一次 track(target, key) 操作。

整個 track 的數據結構大概是這樣

目的就是將 target , key , effect 之間做好對應的關系映射。

打印 targetMap 的結構如下:

**觸發更新 —— trigger

**

上述已經完成了依賴收集,剩下就是監控數據變化,觸發更新操作,即在 setHandler 中添加 trigger 觸發操作。

這樣一來,獲取數據的時候通過 track 進行依賴收集,更新數據的時候再通過 trigger 進行更新,就完成了整個數據的響應式操作。

再回頭看看我們先前提到的例子:

控制臺會依次打印 Hello ***** effect ***** 以及 World ***** effect ***** , 分別是首次渲染觸發跟更新數據重渲染觸發,至此功能實現!

整體來說, Vue3 相比于 Vue2 在很多方面都做了調整,數據的響應式只是冰山一角,但是可以看出尤大團隊非常巧妙的利用了 Proxy 的特點以及 es6 的數據結構和方法。另外, Composition API 的模式跟 React 在某些程度上有異曲同工之妙,這種設計模式讓我們在實際開發使用中更加的方法快捷,值得我們去學習,加油!

最后附上倉庫地址 github ,歡迎各位大佬批評斧正~

Vue的雙向數據綁定原理

Vue 數據雙向綁定主要是指:數據變化更新視圖,視圖變化更新數據

實現原理:采用數據監聽、解析結合訂閱者模式的方式,通過Object.defineProperty()來監聽各個屬性的setter,getter,在數據變動時發布消息給訂閱者,觸發相應的監聽回調。從而實現數據的雙向綁定

Vue 主要通過以下 4 個步驟來實現數據雙向綁定的:

1、實現一個監聽器 Observer:對數據對象進行遍歷,包括子屬性對象的屬性,利用 Object.defineProperty() 對屬性都加上 setter 和 getter。這樣的話,給這個對象的某個值賦值,就會觸發 setter,那么就能監聽到了數據變化。

2、實現一個解析器 Compile:解析 Vue 模板指令,將模板中的變量都替換成數據,然后初始化渲染頁面視圖,并將每個指令對應的節點綁定更新函數,添加監聽數據的訂閱者,一旦數據有變動,收到通知,調用更新函數進行數據更新。

3、實現一個訂閱者 Watcher:Watcher 訂閱者是 Observer 和 Compile 之間通信的橋梁 ,主要的任務是訂閱 Observer 中的屬性值變化的消息,當收到屬性值變化的消息時,觸發解析器 Compile 中對應的更新函數。

4、實現一個訂閱器 Dep:訂閱器采用 發布-訂閱 設計模式,用來收集訂閱者 Watcher,對監聽器 Observer 和 訂閱者 Watcher 進行統一管理。

vue是怎么將數據綁定到組件的原理

vue將數據綁定到組件的原理如下:

1、當實例化一個Vue構造函數,會執行 Vue 的 init 方法,在 init 方法中主要執行三部分內容,一是初始化環境變量,而是處理 Vue 組件數據,三是解析掛載組件。以上三部分內容構成了 Vue 的整個執行過程。

2、Vue 實現了一個 觀察者-消費者(訂閱者) 模式來實現數據驅動視圖。通過設定對象屬性的 setter/getter 方法來監聽數據的變化,而每個屬性的 setter 方法就是一個觀察者, 當屬性變化將會向訂閱者發送消息,從而驅動視圖更新。

3、Vue 的訂閱者 watcher 實現在 /src/watchr.js 。構建一個 watcher 最重要的是 expOrFn 和 cb 兩個參數,cb 是訂閱者收到消息后需要執行的回調,一般來說這個回調都是視圖指令的更新方法,從而達到視圖的更新,但是這也不是必須的,訂閱回調也可以是一個和任何無關的純函數。一個訂閱者最重要的是要知道自己訂閱了什么,watcher 分析 expOrFn 的 getter 方法,從而間接獲得訂閱的對象屬性。

4、Vue 雙向數據綁定實現

數據與視圖的綁定與同步,最終體現在對數據的讀寫處理過程中,也就是 Object.defineProperty() 定義的數據 set、get 函數中。Vue 中對于的函數為 defineReactive,在精簡版實現中,我只保留了一些基本特性:

function defineReactive(obj, key, value)

搜索关键词: