亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

簡易mvvm庫的設(shè)計實(shí)現(xiàn)

caoym / 1370人閱讀

摘要:前言模式即模式簡稱單項雙向數(shù)據(jù)綁定的實(shí)現(xiàn),讓前端開發(fā)者們從繁雜的事件中解脫出來,很方便的處理數(shù)據(jù)和之間的聯(lián)動。本文將從的雙向數(shù)據(jù)綁定入手剖析庫設(shè)計的核心代碼與思路。

前言

mvvm模式即model-view-viewmodel模式簡稱,單項/雙向數(shù)據(jù)綁定的實(shí)現(xiàn),讓前端開發(fā)者們從繁雜的dom事件中解脫出來,很方便的處理數(shù)據(jù)和ui之間的聯(lián)動。
本文將從vue的雙向數(shù)據(jù)綁定入手,剖析mvvm庫設(shè)計的核心代碼與思路。

需求整理與分析 整理

數(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ù)

實(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ù)變化后更新視圖

模塊劃分

MVVM目前劃分為observer,compile,directive,watcher四個模塊

數(shù)據(jù)監(jiān)聽模塊observer

通過es5規(guī)范中的object.defineProperty方式實(shí)現(xiàn)對數(shù)據(jù)的監(jiān)聽
實(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等方法.
具體可以看我上一篇文章js對象監(jiān)聽實(shí)現(xiàn)
observer.js代碼

export function Observer(obj) {
    this.$observe = function(_obj) {
        var type = Object.prototype.toString.call(_obj);
        if (type == "[object Object]") {
            this.$observeObj(_obj);
        } else if (type == "[object Array]") {
            this.$cloneArray(_obj);
        }
    };

    this.$observeObj = function(obj) {
        var t = this;
        Object.keys(obj).forEach(function(prop) {
            var val = obj[prop];
            defineProperty(obj, prop, val);
            if (prop != "__observe__") {
                t.$observe(val);
            }
        });
    };

    this.$cloneArray = function(a_array) {
        var ORP = ["push", "pop", "shift", "unshift", "splice", "sort", "reverse"];
        var arrayProto = Array.prototype;
        var newProto = Object.create(arrayProto);
        ORP.forEach(function(prop) {
            Object.defineProperty(newProto, prop, {
                value: function(newVal) {
                    var dep = a_array.__observe__;
                    var re=arrayProto[prop].apply(a_array, arguments);
                    dep.notify();
                    return re;
                },
                enumerable: false,
                configurable: true,
                writable: true
            });
        });
        a_array.__proto__ = newProto;
    };

    this.$observe(obj, []);
}

var addObserve = function(val) {
    if (!val || typeof val != "object") {
        return;
    }
    var dep = new Dep();
    if (isArray(val)) {
        val.__observe__ = dep;
        return dep;
    }

}

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();
            }
        }
    });
}
編譯模塊compiler

實(shí)現(xiàn)思路:
1.將模版template上的dom遍歷一遍,將其存入文檔碎片frag
2.遍歷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);
          })
        }
      }
    };
    
    var paserNode = function (model, node) {
  var attributes = node.attributes || [];
  var direct_array = [];
  var scope = {
    parentNode: node.parentNode,
    nextNode: node.nextElementSibling,
    el: node,
    model: model,
    direct_array: direct_array
  };

  attributes = toArray(attributes);
  var textContent = node.textContent;
  var attrs = [];
  var vfor;

  attributes.forEach(attr => {
    var name = attr.name;
    if (isDirective(name)) {
      if (name == "v-for") {
        vfor = attr;
      } else {
        attrs.push(attr);
      }
      removeAttribute(node, name);
    }
  });

  //bug  nodeType=3
  var textValue = stringParse(textContent);
  if (textValue) {
    attrs.push({
      name: "v-text",
      value: textValue
    });
    node.textContent = "";
  }

  if (vfor) {
    scope.attrs = attrs;
    attrs = [vfor];
  }

  attrs.forEach(function (attr) {
    var name = attr.name;
    var val = attr.value;
    var directiveType = "v" + /v-(w+)/.exec(name)[1];
    var Directive = directives[directiveType];
    if (Directive) {
      direct_array.push(new Directive(val, scope));
    }
  });
};

var isDirective = function (attr) {
  return /v-(w+)/.test(attr)
};

var isScript = function isScript(el) {
  return el.tagName === "SCRIPT" && (
      !el.hasAttribute("type") ||
      el.getAttribute("type") === "text/javascript"
    )
}
指令模塊directive

指令信息如:v-text,v-for,v-model等。

每種指令信息需要的初始化動作以及指令的刷新函數(shù)update都可能不一樣,所以我們把它抽象出來多帶帶做一個模塊。當(dāng)然也有公用的如公共屬性,統(tǒng)一的watcher實(shí)例化,unbind.

update函數(shù)則具體定義所屬指令如何渲染ui

如簡單的vtext指令的update函數(shù)如下:

vt.update = function (textContent) {
    this.el.textContent = textContent;
};
結(jié)構(gòu)圖

數(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();
            }
        }
    });
}
流程圖

簡單的流程圖如下:

效果圖

代碼地址 總結(jié)

本文基本對mvvm庫的需求整理,拆分,以及對拆分模塊的逐一實(shí)現(xiàn)來達(dá)到整體雙向綁定功能的實(shí)現(xiàn),當(dāng)然目前市場上的mvvm庫功能絕不止于此,本文只是略舉個人認(rèn)為的核心代碼。
如果思路和實(shí)現(xiàn)上的問題,也請各位斧正,謝謝閱讀!

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/86525.html

相關(guān)文章

  • web前端mvc庫實(shí)現(xiàn)

    摘要:如函數(shù)通過名,找到對應(yīng)的數(shù)組,并觸發(fā)所有數(shù)組內(nèi)回調(diào)函數(shù)。核心代碼如下效果圖源碼前端實(shí)現(xiàn)小節(jié)整篇文章基本是圍繞著如下點(diǎn),的觀察者模式的實(shí)現(xiàn)展開,期間的銷毀則取消與之有關(guān)聯(lián)對象的關(guān)系,如銷毀時,注銷掉與之關(guān)聯(lián)的的回調(diào)函數(shù)。 web前端mvc庫實(shí)現(xiàn) 前言 隨著前端應(yīng)用日趨復(fù)雜,如今如angular,vue的mvvm框架,基于virtual dom的react等前端庫基本成為了各個公司的首選。...

    Kylin_Mountain 評論0 收藏0
  • 【教學(xué)向】再加150行代碼教你實(shí)現(xiàn)一個低配版的web component庫(1) —設(shè)計

    摘要:為的內(nèi)置一個方法,用法和原生的事件機(jī)制一毛一樣。 前言 上兩篇Mvvm教程的熱度超出我的預(yù)期,很多碼友留言表揚(yáng)同時希望我繼續(xù)出下一篇教程,當(dāng)時我也半開玩笑說只要點(diǎn)贊超10就兌現(xiàn)承諾,沒想到還真破了10,所以就有了今天的文章。 準(zhǔn)備工作 熟讀 【教學(xué)向】150行代碼教你實(shí)現(xiàn)一個低配版的MVVM庫(1)- 原理篇【教學(xué)向】150行代碼教你實(shí)現(xiàn)一個低配版的MVVM庫(2)- 代碼篇 本篇是在...

    Clect 評論0 收藏0
  • 【教學(xué)向】150行代碼教你實(shí)現(xiàn)一個低配版的MVVM庫(2)- 代碼篇

    摘要:也放出地址,上面有完整工程以及在線演示地址相關(guān)閱讀教學(xué)向行代碼教你實(shí)現(xiàn)一個低配版的庫原理篇教學(xué)向行代碼教你實(shí)現(xiàn)一個低配版的庫代碼篇教學(xué)向再加行代碼教你實(shí)現(xiàn)一個低配版的庫設(shè)計篇教學(xué)向再加行代碼教你實(shí)現(xiàn)一個低配版的庫原理篇 書接上一篇: 150行代碼教你實(shí)現(xiàn)一個低配版的MVVM庫(1)- 原理篇 寫在前面 為了便于分模塊,和閱讀,我使用了Typescript來進(jìn)行coding,總行數(shù)是正好...

    loonggg 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<