剖析Vue原理&實現雙向綁定MVVM-2

vue.js 最核心的功能有兩個,一是響應式的數據綁定系統,二是組件系統。本文僅探究雙向綁定是怎樣實現的。先講涉及的知識點,再用簡化得不能再簡化的代碼實現一個簡單的 hello world 示例。vue

1、訪問器屬性node

       訪問器屬性是對象中的一種特殊屬性,它不能直接在對象中設置,而必須經過 defineProperty() 方法單獨定義。git

       var obj = { };github

       // 爲obj定義一個名爲 hello 的訪問器屬性app

       Object.defineProperty(obj, "hello", {函數

         get: function () {return sth},性能

         set: function (val) {/* do sth */}this

       })spa

       obj.hello // 能夠像普通屬性同樣讀取訪問器屬性3d

       訪問器屬性的"值"比較特殊,讀取或設置訪問器屬性的值,其實是調用其內部特性:get和set函數。

       obj.hello // 讀取屬性,就是調用get函數並返回get函數的返回值

       obj.hello = "abc" // 爲屬性賦值,就是調用set函數,賦值實際上是傳參 

       get 和 set 方法內部的 this 都指向 obj,這意味着 get 和 set 函數能夠操做對象內部的值。另外,訪問器屬性的會"覆蓋"同名的普通屬性,由於訪問器屬性會被優先訪問,與其同名的普通屬性則會被忽略。

 

2、極簡雙向綁定的實現

       此例實現的效果是:隨文本框輸入文字的變化,span 中會同步顯示相同的文字內容;在js或控制檯顯式的修改 obj.hello 的值,視圖會相應更新。這樣就實現了 model => view 以及 view => model 的雙向綁定。

 

       以上就是 Vue 實現雙向綁定的基本原理。

 

3、分解任務

       上述示例僅僅是爲了說明原理。咱們最終要實現的是:

       首先將該任務分紅幾個子任務:

   一、輸入框以及文本節點與 data 中的數據綁定

   二、輸入框內容變化時,data 中的數據同步變化。即 view => model 的變化。

   三、data 中的數據變化時,文本節點的內容同步變化。即 model => view 的變化。

       要實現任務一,須要對 DOM 進行編譯,這裏有一個知識點:DocumentFragment。

 

4、DocumentFragment

       DocumentFragment(文檔片斷)能夠看做節點容器,它能夠包含多個子節點,當咱們將它插入到 DOM 中時,只有它的子節點會插入目標節點,因此把它看做一組節點的容器。使用 DocumentFragment 處理節點,速度和性能遠遠優於直接操做 DOM。Vue 進行編譯時,就是將掛載目標的全部子節點劫持(真的是劫持,經過 append 方法,DOM 中的節點會被自動刪除)到 DocumentFragment 中,通過一番處理後,再將 DocumentFragment 總體返回插入掛載目標。

 

# 勘誤:flag.append() 應爲 flag.appendChild()。下同。在 Chrome 中用 append() 居然正常,沒報錯。

      

5、數據初始化綁定

       以上代碼實現了任務一,咱們能夠看到,hello world已經呈如今輸入框和文本節點中。

 

6、響應式的數據綁定

       再來看任務二的實現思路:當咱們在輸入框輸入數據的時候,首先觸發 input 事件(或者 keyup、change 事件),在相應的事件處理程序中,咱們獲取輸入框的 value 並賦值給 vm 實例的 text 屬性。咱們會利用 defineProperty 將 data 中的 text 設置爲 vm 的訪問器屬性,所以給 vm.text 賦值,就會觸發 set 方法。在 set 方法中主要作兩件事,第一是更新屬性的值,第二留到任務三再說。

       任務二也就完成了,text 屬性值會與輸入框的內容同步變化:

 

7、訂閱/發佈模式(subscribe&publish)

       text 屬性變化了,set 方法觸發了,可是文本節點的內容沒有變化。如何讓一樣綁定到 text 的文本節點也同步變化呢?這裏又有一個知識點:訂閱發佈模式。

       訂閱發佈模式(又稱觀察者模式)定義了一種一對多的關係,讓多個觀察者同時監聽某一個主題對象,這個主題對象的狀態發生改變時就會通知全部觀察者對象。

       發佈者發出通知 => 主題對象收到通知並推送給訂閱者 => 訂閱者執行相應操做

       以前提到的,當 set 方法觸發後作的第二件事就是做爲發佈者發出通知:「我是屬性 text,我變了」。文本節點則是做爲訂閱者,在收到消息後執行相應的更新操做。

 

8、雙向綁定的實現

       回顧一下,每當 new 一個 Vue,主要作了兩件事:第一個是監聽數據:observe(data),第二個是編譯 HTML:nodeToFragement(id)。

       在監聽數據的過程當中,會爲 data 中的每個屬性生成一個主題對象 dep。

       在編譯 HTML 的過程當中,會爲每一個與數據綁定相關的節點生成一個訂閱者 watcher,watcher 會將本身添加到相應屬性的 dep 中。

       咱們已經實現:修改輸入框內容 => 在事件回調函數中修改屬性值 => 觸發屬性的 set 方法。

       接下來咱們要實現的是:發出通知 dep.notify() => 觸發訂閱者的 update 方法 => 更新視圖。

       這裏的關鍵邏輯是:如何將 watcher 添加到關聯屬性的 dep 中。

       在編譯 HTML 過程當中,爲每一個與 data 關聯的節點生成一個 Watcher。Watcher 函數中發生了什麼呢?

       首先,將本身賦給了一個全局變量 Dep.target;

       其次,執行了 update 方法,進而執行了 get 方法,get 的方法讀取了 vm 的訪問器屬性,從而觸發了訪問器屬性的 get 方法,get 方法中將該 watcher 添加到了對應訪問器屬性的 dep 中;

       再次,獲取屬性的值,而後更新視圖。

       最後,將 Dep.target 設爲空。由於它是全局變量,也是 watcher 與 dep 關聯的惟一橋樑,任什麼時候刻都必須保證 Dep.target 只有一個值。

       至此,hello world 雙向綁定就基本實現了。文本內容會隨輸入框內容同步變化,在控制器中修改 vm.text 的值,會同步反映到文本內容中。

    完整代碼:https://github.com/bison1994/two-way-data-binding

相關文章
相關標籤/搜索