您好,登錄后才能下訂單哦!
作者:刀哥(朱建)
前言:mvvm模式即model-view-viewmodel模式簡稱,單項(xiàng)/雙向數(shù)據(jù)綁定的實(shí)現(xiàn),讓前端開發(fā)者們從繁雜的dom事件中解脫出來,很方便的處理數(shù)據(jù)和ui之間的聯(lián)動。本文將從vue的雙向數(shù)據(jù)綁定入手,剖析mvvm庫設(shè)計(jì)的核心代碼與思路。
1、需求整理與分析
需求:
數(shù)據(jù)一旦改變則更新數(shù)據(jù)對應(yīng)的ui
分析:
通過dom節(jié)點(diǎn)的指令獲取刷新函數(shù),用來刷新指定的ui。
實(shí)現(xiàn)一個橋接的方法,讓刷新函數(shù)和需要的數(shù)據(jù)關(guān)聯(lián)起來。
監(jiān)聽數(shù)據(jù)變化,數(shù)據(jù)改變后通過橋接方法調(diào)用刷新函數(shù)。
2、實(shí)現(xiàn)思路
實(shí)現(xiàn)observer,重新定義data,為data上每個屬性增加setter,getter以監(jiān)聽數(shù)據(jù)的變化。
實(shí)現(xiàn)compile,掃描模版template,提取每個dom節(jié)點(diǎn)中的指令信息。
實(shí)現(xiàn)directive,通過指令信息是實(shí)例化對應(yīng)的directive實(shí)例,不同類型的directive擁有不同的刷新函數(shù)update。
3、模塊劃分
MVVM目前劃分為observer,compile,directive,watcher四個模塊。
4、數(shù)據(jù)監(jiān)聽模塊observer
通過es5規(guī)范中的object.defineProperty方式實(shí)現(xiàn)對數(shù)據(jù)的監(jiān)聽。
5、實(shí)現(xiàn)思路
遞歸遍歷data,將data下面所有屬性都加上set,get方法,以實(shí)現(xiàn)對所有屬性的攔截.
注意:對象可能含有數(shù)組屬性,數(shù)組的內(nèi)置有push,pop,splice等方法改變內(nèi)部數(shù)據(jù).
此時做法是改變數(shù)組的原型鏈,在原型鏈中增加一層自定義的push,pop,splice方法做攔截,這些方法里面加上我們自己的回調(diào)函數(shù),然后在調(diào)用原生的push,pop,splice等方法。
export function defineProperty(obj, prop, val) {
if (prop == '__observe__') {
return;
}
val = val || obj[prop];
var dep = new Dep();
obj.__observe__ = dep;
var childDep = addObserve(val);
Object.defineProperty(obj, prop, {
get: function() {
var target = Dep.target;
if (target) {
dep.addSub(target);
if (childDep) {
childDep.addSub(target);
}
}
return val;
},
set: function(newVal) {
if(newVal!=val){
val = newVal;
dep.notify();
}
}
});
}
6、編譯模塊compiler
實(shí)現(xiàn)思路:
將模版template上的dom遍歷一遍,將其存入文檔碎片frag
var complieTemplate = function (nodes, model) {
if ((nodes.nodeType == 1 || nodes.nodeType == 11) && !isScript(nodes)) {
paserNode(model, nodes);
if (nodes.hasChildNodes()) {
nodes.childNodes.forEach(node=> {
complieTemplate(node, model);
})
}
}
};
7、指令模塊directive
指令信息如:v-text,v-for,v-model等。
每種指令信息需要的初始化動作以及指令的刷新函數(shù)update都可能不一樣,所以我們把它抽象出來單獨(dú)做一個模塊。當(dāng)然也有公用的如公共屬性,統(tǒng)一的watcher實(shí)例化,unbind.
update函數(shù)則具體定義所屬指令如何渲染ui,如簡單的vtext指令的update函數(shù)如下:
vt.update = function (textContent) {
this.el.textContent = textContent;
};
9、結(jié)構(gòu)圖
cdn.xitu.io/2019/9/3/16cf5cc170b0c95d?w=640&h=522&f=png&s=22778">)
9、數(shù)據(jù)訂閱模塊watcher
watcher的功能是讓directive和observer模塊關(guān)聯(lián)起來。初始化的時候做兩件事:
將directive模塊的update函數(shù)當(dāng)參數(shù)傳入,并將其存入自身update屬性中。
由于在defineProperty函數(shù)中定義的dep變量在setter和getter函數(shù)里有引用,使dep變量處于閉包狀態(tài)沒有釋放,此時在getter方法中通過判斷Depend.target的存在,來獲取訂閱者watcher,通過發(fā)布者dep儲存起來。數(shù)據(jù)的每個屬性都有一個唯一的的dep變量,記錄著所有訂閱者watcher的信息,一旦屬性有變化,調(diào)用setter函數(shù)的時候觸發(fā)dep.notify(),通知所有已訂閱的watcher,進(jìn)而執(zhí)行所有與該屬性關(guān)聯(lián)的刷新函數(shù),最后更新指定的ui。
watcher 初始化部分代碼:
Depend.target = this;
this.value = this.getValue();
Depend.target = null;
observer.js 屬性定義代碼:
export function defineProperty(obj, prop, val) {
if (prop == '__observe__') {
return;
}
val = val || obj[prop];
var dep = new Dep();
obj.__observe__ = dep;
var childDep = addObserve(val);
Object.defineProperty(obj, prop, {
get: function() {
var target = Dep.target;
if (target) {
dep.addSub(target);
if (childDep) {
childDep.addSub(target);
}
}
return val;
},
set: function(newVal) {
if(newVal!=val){
val = newVal;
dep.notify();
}
}
});
}
10、流程圖
11、總結(jié)
文基本對mvvm庫的需求整理,拆分,以及對拆分模塊的逐一實(shí)現(xiàn)來達(dá)到整體雙向綁定功能的實(shí)現(xiàn),當(dāng)然目前市場上的mvvm庫功能絕不止于此,本文只是略舉個人認(rèn)為的核心代碼。如果思路和實(shí)現(xiàn)上的問題,也請各位斧正,謝謝閱讀!
原代碼:https://github.com/laughing-pic-zhu/mvvm
想要深入了解的同學(xué)可以訪問數(shù)瀾社區(qū),和大家一起討論學(xué)習(xí)~
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。