溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JS源碼分析│簡易mvvm庫的設(shè)計(jì)實(shí)現(xiàn)

發(fā)布時間:2020-07-04 19:13:03 來源:網(wǎng)絡(luò) 閱讀:175 作者:數(shù)瀾 欄目:大數(shù)據(jù)

作者:刀哥(朱建)

前言: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

  • ui改變則觸發(fā)事件改變ui對應(yīng)的數(shù)據(jù)

分析:

  • 通過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ù)。

  • ui改變觸發(fā)對應(yīng)的dom事件在改變特定的數(shù)據(jù)。

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。

  • 實(shí)現(xiàn)watcher,讓observer的屬性監(jiān)聽函數(shù)與directive的update函數(shù)做一一對應(yīng),以實(shí)現(xiàn)數(shù)據(jù)變化后更新視圖。

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

  • 遍歷frag,通過attributes獲取節(jié)點(diǎn)的屬性信息,在通過正則表達(dá)式過濾屬性信息,進(jìn)而拿到元素節(jié)點(diǎn)和文檔節(jié)點(diǎn)的指令信息
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)圖

JS源碼分析│簡易mvvm庫的設(shè)計(jì)實(shí)現(xiàn)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屬性中。

  • 調(diào)用getValue,從而獲取對象data的特定屬性值,進(jìn)而觸發(fā)一次之前在observer定義的屬性函數(shù)的getter方法。

由于在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、流程圖

JS源碼分析│簡易mvvm庫的設(shè)計(jì)實(shí)現(xiàn)

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í)~

向AI問一下細(xì)節(jié)

免責(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)容。

AI