本章涉及知識點(diǎn)
1、前言
2、ZVue架構(gòu)設(shè)計
3、框架的功能列表
4、ZVue類的屬性定義
5、_observer方法定義
6、_complie方法定義
7、v-model和v-bind指令解析
8、v-for循環(huán)指令解析
9、v-click點(diǎn)擊指令解析
10、Watcher類的屬性定義
11、Watcher動態(tài)批量刪除DOM方法
12、Watcher動態(tài)批量添加DOM方法
13、Watcher動態(tài)更新DOM方法
14、變異數(shù)組類的屬性定義
15、變異數(shù)組切面調(diào)用回調(diào)函數(shù)
16、案例頁面引用ZVue框架
17、案例結(jié)果的展示分析
18、后記
一、前言
寫完了幾篇數(shù)學(xué)算法的推導(dǎo),突發(fā)奇想來換個角度寫一點(diǎn)關(guān)于前端的框架原理算法,于是就嘗試寫了這個輕量的框架—ZVue,大致上模擬了Vue漸進(jìn)式框架的MVVM邏輯流程,也是自己對Vue框架原理的一些感悟和心得體會
該框架可以實(shí)現(xiàn)動態(tài)的data劫持監(jiān)聽和指令解析、增刪改查整個數(shù)組字面量或者若干數(shù)組項(xiàng)字面量的時候,同時刷新view對應(yīng)的視圖、事件的動態(tài)綁定,以及data和view的雙向綁定。在page層實(shí)例ZVue的語法,html指令模板語法,包括渲染到瀏覽器端后的dom的結(jié)構(gòu)和原生Vue基本上相同
二、ZVue整體架構(gòu)設(shè)計
梳理一下MVVM流程,需要動態(tài)劫持,解析指令,更新視圖,變異數(shù)組處理等,設(shè)計出的架構(gòu)圖如下

根據(jù)架構(gòu)圖,類和函數(shù)的設(shè)計清單如下:
1:ZVue類
(1)constructor:初始化ZVue實(shí)例的數(shù)據(jù)集合、方法集合、template映射表和render映射表等
(2)_toNewArray:實(shí)例變異數(shù)組及注入回調(diào)函數(shù)
(3)_defineProperty:屬性劫持到變化,通知相應(yīng)的watcher對象刷新view
(4)_observer:深度遍歷所有屬性,維護(hù)render映射表,并分派劫持監(jiān)聽
(5)_complie:遍歷dom結(jié)構(gòu)的所有指令,維護(hù)template映射表,分派到各自的解析函數(shù)
(6)_parsing_v_model:解析v-model指令,綁定input事件和watcher對象
(7)_parsing_v_click:解析v-click指令,綁定click事件,解析事件的參數(shù)列表
(8)_parsing_v_bind:解析v-bind指令和對應(yīng)的watcher對象
(9)_parsing_v_for:解析v-for指令,作用于整個數(shù)組,循環(huán)生成動態(tài)dom,為父節(jié)點(diǎn)綁定watcher對象
(10)_parsing_v_for_item:遍歷v-for下的動態(tài)dom,綁定watcher對象
(11)_deleteArrayWatcher:動態(tài)維護(hù)刪除render的映射表
(12)_maintainTemplate:動態(tài)維護(hù)template映射表,并為真實(shí)dom設(shè)置v-data唯一標(biāo)志
二、Watcher類
(1)constructor:初始化data綁定的視圖片段,包含真實(shí)的dom,dom的屬性,data的key,ZVue的指針等
(2)getTemplateDom:根據(jù)dom字符串,動態(tài)組裝出包含標(biāo)簽、類名、內(nèi)容以及自定義指令的dom模板
(3)deleteBatchDom:動態(tài)批量刪除dom,并同步維護(hù)render映射表
(4)addBatchDom:動態(tài)批量添加dom,并同步劫持監(jiān)聽新的屬性值以及解析新的指令
(5)update_Array:更新數(shù)組類型的視圖片段
(6)update_Text:更新普通標(biāo)簽中的視圖片段
(7)update:set鉤子通知觸發(fā),負(fù)責(zé)通知屬性對應(yīng)的更新函數(shù)來刷新視圖
三、NewArray類(繼承Array類)
(1)constructor:接受參數(shù)列表和ZVue注入的回調(diào)函數(shù),調(diào)用父級的構(gòu)造函數(shù)
(2)push:重載Array類的push方法,并在切面調(diào)用ZVue的回調(diào)函數(shù)
(3)pop:重載Array類的pop方法,并在切面調(diào)用ZVue的回調(diào)函數(shù)
(4)splice:重載Array類的splice方法,并在切面調(diào)用ZVue的回調(diào)函數(shù)
三、架構(gòu)的功能列表
有了架構(gòu)圖和函數(shù)設(shè)計列表,此架構(gòu)的功能列表為:
(1)頁面ZVue對象的data和methods接管
(2)頁面的模板節(jié)點(diǎn)結(jié)構(gòu)、插值表達(dá)式的正確渲染和節(jié)點(diǎn)事件的綁定
(3)實(shí)現(xiàn)v-model、v-bind、v-click、v-for四個指令的正確解析渲染
(4)Input輸入測試v-model雙向綁定,頁面的值隨著Input的輸入值變化而同步變化
(5)dl列表測試v-for循環(huán)生成若干個dd(包含class和若干指令),且插值表達(dá)式包含常量+索引變量+數(shù)組項(xiàng)數(shù)值變量
(6)button點(diǎn)擊測試更新整個數(shù)組的字面量,頁面的列表同步變化
(7)button點(diǎn)擊測試局部更新數(shù)組的某一項(xiàng)字面量,頁面的列表同步變化
(8)button點(diǎn)擊測試push數(shù)組,頁面的列表同步變化
(9)button點(diǎn)擊測試pop數(shù)組,頁面的列表同步變化
(10)button點(diǎn)擊測試splice數(shù)組,頁面的列表同步變化
四、ZVue類的屬性定義

需要注意原始數(shù)組類型需要賦值為變異數(shù)組,才能進(jìn)行數(shù)組API的切面編程
五、_observer方法定義

需要注意遍歷到數(shù)組類型的時候,需要初始化整個數(shù)組指針的render映射表并深度遍歷,到遍歷到數(shù)組項(xiàng)的時候,render映射表需要額外deep一層,顯然普通類型、數(shù)組指針和數(shù)組任意一項(xiàng)都被劫持,渲染后data結(jié)果為

六、_complie方法定義

分派到各自的指令函數(shù)解析后,render映射表為

七、v-model和v-bind指令解析


可以看到指令解析里,注入了與data對應(yīng)的watcher對象負(fù)責(zé)更新對應(yīng)的view
八、v-for循環(huán)指令解析

需要注意的是_parsing_v_for需要解析參數(shù)列表(可能包含數(shù)組索引或者數(shù)值,或者同時都包含),最后是給節(jié)點(diǎn)的父節(jié)點(diǎn)綁定了watcher對象,該對象只負(fù)責(zé)刪除原來舊列表和生成新列表,而生成的列表節(jié)點(diǎn)還沒有被劫持和解析指令,所以需要重新劫持和解析新的節(jié)點(diǎn)

可以看到_parsing_v_for_item給每一個新節(jié)點(diǎn)注入了對應(yīng)的watcher對象,同時還檢測了新節(jié)點(diǎn)是否包含v-click指令,如果包含則調(diào)用其的解析函數(shù),這也就是架構(gòu)圖中_parsing_v_for_item需要在watcher動態(tài)添加節(jié)點(diǎn)之后調(diào)用
九、v-click點(diǎn)擊指令解析


可以看到v-click指令需要先解析待調(diào)用的函數(shù)名字和參數(shù)列表,同時要注意向上檢測父節(jié)點(diǎn)來判斷當(dāng)前節(jié)點(diǎn)是否是循環(huán)生成的這種情況,因?yàn)槿绻茄h(huán)生成的節(jié)點(diǎn),需要二次從v-for指令中解析轉(zhuǎn)義參數(shù)列表為迭代數(shù)組的索引和數(shù)組項(xiàng),最后在調(diào)用原生的方法并傳入解析轉(zhuǎn)義的參數(shù)列表
十、Watcher類的屬性定義

需要注意repalce是保存了迭代數(shù)組的key名稱集合,來更新替換循環(huán)dom的插值表達(dá)式
十一、Watcher動態(tài)批量刪除DOM方法

該方法是配合v-for指令使用,注意需要在刪除舊dom的同時,需要維護(hù)ZVue的render映射表
十二、Watcher動態(tài)批量添加DOM方法

該方法是配合v-for指令使用,注意需要先從template映射表中讀取到dom的字符串模板,在動態(tài)組裝成dom節(jié)點(diǎn),并根據(jù)新數(shù)組的長度循環(huán)生成新dom,而這些新dom此刻并沒有被ZVue劫持屬性和解析指令,所以在循環(huán)結(jié)束后需要同步劫持新的屬性值和解析新dom的指令
其中動態(tài)根據(jù)dom字符串來正則解析組裝dom的方法定義為

十三、Watcher動態(tài)更新DOM方法

需要判斷當(dāng)數(shù)據(jù)屬于數(shù)組類型,而數(shù)組類型的更新需要動態(tài)刪除、添加、維護(hù)render映射表、重新劫持和解析指令,所以單獨(dú)封裝出數(shù)組的更新方法為

十四、變異數(shù)組類的屬性定義

需要注意到如splice方法的底層實(shí)現(xiàn)也許使用了中間數(shù)組來橋接原數(shù)組,這樣會導(dǎo)致重復(fù)執(zhí)行回調(diào)函數(shù),而變異數(shù)組的聲明是在ZVue的構(gòu)造函數(shù)或者set鉤子里,所以才可以帶上具名的回調(diào)函數(shù),為此,需要判斷如果是變異數(shù)組內(nèi)部構(gòu)造了另一個變異數(shù)組,則不會帶上回調(diào)函數(shù)的情況
十五、變異數(shù)組切面調(diào)用回調(diào)函數(shù)

這樣調(diào)用變異數(shù)組的push、pop、splice方法都會在執(zhí)行完原生數(shù)組的這些方法之后調(diào)用我們的回調(diào)函數(shù),而這個回調(diào)函數(shù)要做的,就是更新ZVue中對應(yīng)的數(shù)組,并且是數(shù)組本身字面量的直接覆蓋,才能讓數(shù)組本身發(fā)生變化,從而進(jìn)入其set鉤子里去通知相應(yīng)的watcher對象刷新其視圖片段
下面是我們ZVue回調(diào)函數(shù)的定義


我們只希望數(shù)組本身被注入set和get鉤子,為此我們將data的名稱當(dāng)做變量作為回調(diào)函數(shù)的名稱,使用eval來聲明具名回調(diào)函數(shù),并為該回調(diào)函數(shù)注入靜態(tài)變量—ZVue實(shí)例指針,這樣就可以在回調(diào)函數(shù)內(nèi)部訪問到ZVue的屬性
十六、案例頁面引用ZVue框架
下面我們編寫案例頁面來引用ZVue

頁面html代碼部分和Vue非常相似~可以寫不同的指令和插值表達(dá)式
下面我們在頁面內(nèi)引入ZVue并實(shí)例


可以看到,頁面的js代碼部分和原生Vue非常相似~其中我們用隨機(jī)數(shù)來作為數(shù)組項(xiàng)來模擬測試增刪改查
十七、案例結(jié)果的展示分析

觀察渲染的dom結(jié)構(gòu)為

可以看到渲染后dom結(jié)構(gòu)和Vue非常接近。下面我們輸入input或者點(diǎn)擊增加減少按鈕來測試頁面的雙向綁定

下面我們來測試數(shù)組的變化,隨意push三個隨機(jī)值到數(shù)組中,觀察數(shù)組的set和render映射表



可以看到新加入的數(shù)組項(xiàng)都被劫持和映射到render表中,下面我們點(diǎn)擊局部更新按鈕來更新數(shù)組的第二項(xiàng)

下面我們pop出四個值,得到

下面我們splice刪除數(shù)組第二項(xiàng),得到

最后我們更新整個數(shù)組字面量為一個長度為4的新數(shù)組,得到


可以看到我們更新覆蓋完整個數(shù)組字面量后,data中l(wèi)ist里又動態(tài)注入了新數(shù)據(jù)的set鉤子且刪除了舊數(shù)據(jù)的set鉤子,render映射表也動態(tài)維護(hù)了新節(jié)點(diǎn)對應(yīng)的watcher對象且刪除了舊節(jié)點(diǎn)的watcher對象
十八、后記
寫完ZVue,主要目的是從原理出發(fā)用原生JS來實(shí)現(xiàn)數(shù)據(jù)的劫持和指令的解析,以及數(shù)組的動態(tài)CURD映射到視圖的動態(tài)變化,并沒有加入虛擬dom的diff算法,同時也是向Vue的作者尤雨溪致敬!