這次我們一起做一個最小的 vue 。
首先我們要明白幾個概念。數(shù)據(jù)驅(qū)動,響應(yīng)式原理,發(fā)布訂閱模式和觀察者模式。
數(shù)據(jù)驅(qū)動
數(shù)據(jù)響應(yīng)式,雙向綁定,數(shù)據(jù)驅(qū)動。
數(shù)據(jù)響應(yīng)式:數(shù)據(jù)模型是普通的 JS 對象,數(shù)據(jù)變,視圖變。
雙向綁定:數(shù)據(jù)變,視圖變。視圖變,數(shù)據(jù)變。如 v-moudle.
數(shù)據(jù)驅(qū)動:只管數(shù)據(jù)變,數(shù)據(jù)怎么渲染到視圖中,不需要理會。這也是為什么 MVVM 框架火熱。
響應(yīng)式核心原理
vue 2.x :按照官網(wǎng)的說法是基于 object.defineProperty?(不兼容 IE8 )把 data 中的屬性遍歷,轉(zhuǎn)化為 getter 和 setter 。
object.defineProperty?:? ?https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
具體代碼如下圖 2.1

vue 3.x?:是基于 ES6 中的 Proxy ,因為它針對象,而不是屬性,所以面對多個對象可以省去遍歷。
Proxy?可學(xué)習(xí)阮一峰老師整理的內(nèi)容? ? ? ?https://es6.ruanyifeng.com/#docs/proxy
代碼如下圖 2.2

發(fā)布訂閱模式
在一個“信息中心”里存在發(fā)布者和訂閱者兩種角色,當(dāng)有新的消息,發(fā)布著會進行推送,訂閱者會進行接收。如圖 2.3

實現(xiàn) vue 的 $on? $emit 見圖2.4

觀察者模式
沒有“信息中心”,目標(biāo)(發(fā)布者)需要知道觀察者(訂閱者)的存在。目標(biāo)中存儲觀察者名單 subs,還有兩個方法, addSub() 添加觀察者, notify() 當(dāng)事件發(fā)生調(diào)用觀察者的? ? ??update() 方法
代碼見下圖2.5

好,在梳理上述概念時候,想必大家對 vue 最基本的構(gòu)成也就有了一個大概認知。
首先一個 vue 類型對象,負責(zé)注入數(shù)據(jù),把數(shù)據(jù)轉(zhuǎn)換成? getter setter ,調(diào)用 observer??和? compiler?。
observer 數(shù)據(jù)劫持監(jiān)聽數(shù)據(jù)變化,通知 dep(發(fā)布者)。
compiler 解析指令,替換插值表達式。
dep 負責(zé)添加觀察者,并調(diào)用觀察者 update。
watcher 更新視圖。
綜上可總結(jié)出 關(guān)系圖2.6

我們先創(chuàng)建一個 使用了 vue 功能的模板,如下圖 2.7

我們來回想一下 vue 的基本構(gòu)造。
每個 vue 實例下邊都包含了 options 、 data 、el 三個屬性。
根據(jù) 圖2.6 ,以及上述總結(jié),我們可以得知 vue 實現(xiàn)了什么以下四步功能。
1. 通過屬性保存選項的數(shù)據(jù)
2. 把 data 中的成員轉(zhuǎn)換成 getter 和 setter ,注入到 vue 實例中
3.?調(diào)用 observer 對象,監(jiān)聽數(shù)據(jù)的變化
4. 調(diào)用 compiler 對象,解析指令和差值表達式
有了以上四步,邏輯就很清晰了。然后可以得到代碼 圖2.8

除了紅框里的內(nèi)容,其他的代碼在上邊都解釋了,這里也就不做贅述。
接下來我們來解決紅框里的兩個類。
首先是 Observer ,我們想一下它做了什么 ,數(shù)據(jù)劫持監(jiān)聽數(shù)據(jù)變化,并通知?dep(發(fā)布者)。
所謂的監(jiān)聽數(shù)據(jù)變化。也就是把 data 中的數(shù)據(jù)轉(zhuǎn)化為 getter 和 setter ,方便 this 調(diào)用。但是這里與 vue 類中不同的是。 Observer 還和 Dep 關(guān)聯(lián)。
我們來想一下, Dep 做了什么,無非就是添加觀察者和發(fā)布。
添加的時機顯然就是生成 getter 的時候, 發(fā)布就是 set 的時候。由此得出代碼 圖2.9

好,出了上述代碼,大家一定對紅框框里的 dep 比較感興趣。對吧,你憑什么就突然一個 Dep.target ,然后就開始添加觀察者,這顯然不講道理。
好,講道理之前我們先看兩個其他的類,一個 Dep ,一個 watcher
Dep 無非就是添加和發(fā)布,上邊說了,不再贅述,直接上才藝,圖2.10

簡單樸實不難懂。然后是 watcher ,它是來干嘛的,顯然是監(jiān)控數(shù)據(jù)變化。數(shù)據(jù)變化了然后編譯到頁面上。不難得出 watcher 的構(gòu)成就是,屬性,以及更新 DOM 的函數(shù)。
想要更新 DOM ,就少不了 Vue 實例和變化的屬性名稱,為了方便處理不同節(jié)點以及減少數(shù)據(jù)重復(fù)傳遞,我們一般會在實例化 watcher 時傳入處理函數(shù)。由此得出 圖 2.11

這里就解釋清楚為什么會有?Dep.target?&&?dep.addSub(Dep.target) 這樣的代碼了。
最后一部分,也就是剩下的 compiler 類了,它主要就是用來編譯節(jié)點,根節(jié)點 el ,實例 vm 。這都是必須的沒什么好說的,設(shè)下的功能就是為了編譯。
vue 中的節(jié)點中我們只需要處理兩種,文本節(jié)點和帶有 v- 指令的元素節(jié)點,所以整體代碼結(jié)構(gòu)如下圖,2.12.

三個判斷函數(shù),如下圖2.13,

主函數(shù),根據(jù)條件進行一個函數(shù)分發(fā)和遞歸

文本節(jié)點的處理,通過正則匹配 {} 中的內(nèi)容,如果內(nèi)容存在,就用他的值,替換到 node.textcontent 中,同時創(chuàng)建一個 watcher 對象對該數(shù)據(jù)的后續(xù)變化進行監(jiān)聽并處理。

編譯節(jié)點,遍歷屬性節(jié)點,找到是 v- 指令的,進行編譯,這里通過 update 函數(shù)進行了一次分發(fā),因為使用 if 分發(fā)太麻煩了,所有制令都要占行數(shù),

這兩個函數(shù)也比較簡單,就是根據(jù)情況進行一個修飾,

把寫好的類引入到 index 里,好,大公告成。
學(xué)了就問老板要漲薪,隔壁員工都饞哭了?。?!