vue實現原理解析及一步步實現vue框架

深刻解析vue 1實現原理,並實現vue雙向數據綁定模型 vueImitate,此模型(vueImitate)只適用於學習和了解vue實現原理;沒法做爲項目中使用,沒有進行任何異常錯誤處理及各類使用場景的兼容;但經過此項目,可讓你:
  • 深刻了解vue實現原理
  • 親手一步一步本身實現vue相應功能,包括雙向綁定、指令如v-model、v-show、v-bind等

總體效果以下:html

圖片描述

下面咱們重頭開始框架的實現,咱們知道,vue的使用方式以下:vue

var vm = new Vue({
    el: 'root',
    data() {
        return {
            message: 'this is test',
            number: 5,
            number1: 1,
            number2: 2,
            showNode: false
        }
    },
    methods: {
        add() {
            this.number1 += 1;
            this.number += 1;
        },
        show() {
            this.showNode = !this.showNode;
        }
    }
})

因而可知,vue爲一個構造函數,而且調用時傳入一個對象參數,因此主函數vueImitate能夠以下,源碼可見這裏;並對參數進行對應的初始化處理:node

// init.js 
export default function vueImitate(options) {
    this.options = options || {}; 
    this.selector = options.el ? ('#' + options.el) : 'body'; // 根節點selector
    this.data = typeof options.data === 'function' ? options.data() : options.data; // 保存傳入的data
    this.el = document.querySelectorAll(this.selector)[0]; // 保存根節點

    this._directives = [];
}

此時可使用new vueImitate(options)的方式進行調用,首先,咱們須要界面上展現正確的數據,也就是將下面頁面進行處理,使其能夠正常訪問;git

圖片描述

咱們能夠參考vue的實現方式,vue將{{ }}這種綁定數據的方式轉化爲指令(directive),即v-text相似;而v-text又是如何進行數據綁定的呢?經過下面代碼可知,是經過對文本節點從新賦值方式實現,源碼見這裏github

export default {
  bind () {
    this.attr = this.el.nodeType === 3
      ? 'data'
      : 'textContent'
  },
  update (value) {
    this.el[this.attr] = value
  }
}

那麼,問題來了,若是須要按照上面的方式實現數據的綁定,咱們須要將如今的字符串{{number}}轉化爲一個文本節點,並對它進行指令化處理;這些其實也就是vue compile(編譯)、link過程完成的,下面咱們就先實現上面功能需求;express

compile

整個編譯過程確定從根元素開始,逐步向子節點延伸處理;數組

export default function Compile(vueImitate) {
    vueImitate.prototype.compile = function() {
        let nodeLink = compileNode(this.el),
            nodeListLink = compileNodeList(this.el.childNodes, this),
            _dirLength = this._directives.length;

        nodeLink && nodeLink(this);
        nodeListLink && nodeListLink(this);

        let newDirectives = this._directives.slice(_dirLength);

        for(let i = 0, _i = newDirectives.length; i < _i; i++) {
            newDirectives[i]._bind();
        }
    }
}

function compileNode(el) {
    let textLink, elementLink;
    // 編譯文本節點
    if(el.nodeType === 3 && el.data.trim()) {
        textLink = compileTextNode(el);
    } else if(el.nodeType === 1) {
        elementLink = compileElementNode(el);
    }
    return function(vm) {
        textLink && textLink(vm);
        elementLink && elementLink(vm);
    }
}

function compileNodeList(nodeList, vm) {
    let nodeLinks = [], nodeListLinks = [];
    if(!nodeList || !nodeList.length) {
        return;
    }
    for(let i = 0, _i = nodeList.length; i < _i; i++) {
        let node = nodeList[i];
        nodeLinks.push(compileNode(node)),
        nodeListLinks.push(compileNodeList(node.childNodes, vm));
    }
    return function(vm) {
        if(nodeLinks && nodeLinks.length) {
            for(let i = 0, _i = nodeLinks.length; i < _i; i++) {
                nodeLinks[i] && nodeLinks[i](vm);
            }
        }
        if(nodeListLinks && nodeListLinks.length) {
            for(let i = 0, _i = nodeListLinks.length; i < _i; i++) {
                nodeListLinks[i] && nodeListLinks[i](vm);
            }
        }
    }
}

如上代碼,首先,咱們經過定義一個Compile函數,將編譯方法放到構造函數vueImitate.prototype,而方法中,首先主要使用compileNode編譯根元素,而後使用compileNodeList(this.el.childNodes, this)編譯根元素下面的子節點;而在compileNodeList中,經過對子節點進行循環,繼續編譯對應節點及其子節點,以下代碼:性能優化

//  function compileNodeList
for(let i = 0, _i = nodeList.length; i < _i; i++) {
    let node = nodeList[i];
    nodeLinks.push(compileNode(node)),
    nodeListLinks.push(compileNodeList(node.childNodes, vm));
}

而後進行遞歸調用,直到最下層節點:而在對節點進行處理時,主要分爲文本節點和元素節點;文本節點主要處理上面說的{{number}}的編譯,元素節點主要處理節點屬性如v-modelv-textv-showv-bind:click等處理;app

function compileTextNode(el) {
    let tokens = parseText(el.wholeText);
    var frag = document.createDocumentFragment();
    for(let i = 0, _i = tokens.length; i < _i; i++) {
        let token = tokens[i], el = document.createTextNode(token.value)
        frag.appendChild(el);
    }

    return function(vm) {
        var fragClone = frag.cloneNode(true);
        var childNodes = Array.prototype.slice.call(fragClone.childNodes), token;
        for(let j = 0, _j = tokens.length; j < _j; j++) {
            if((token = tokens[j]) && token.tag) {
                let    _el = childNodes[j], description = {
                    el: _el,
                    token: tokens[j],
                    def: publicDirectives['text']
                }
                vm._directives.push(new Directive(vm, _el, description))
            }
        }

        // 經過這兒將`THIS IS TEST {{ number }} test` 這種轉化爲三個textNode
        if(tokens.length) {
            replace(el, fragClone);
        }
    }    
}

function compileElementNode(el) {
    let attrs = getAttrs(el);
    return function(vm) {
        if(attrs && attrs.length) {
            attrs.forEach((attr) => {
                let name = attr.name, description, matched;
                if(bindRE.test(attr.name)) {
                    description = {
                        el: el,
                        def: publicDirectives['bind'],
                        name: name.replace(bindRE, ''),
                        value: attr.value
                    }
                } else if((matched = name.match(dirAttrRE))) {
                    description = {
                        el: el,
                        def: publicDirectives[matched[1]],
                        name: matched[1],
                        value: attr.value
                    }
                }
                if(description) {
                    vm._directives.push(new Directive(vm, el, description));

                }
            })
        }
    }
}

這裏,先主要說明對文本節點的處理,咱們上面說過,咱們須要對{{number}}之類進行處理,咱們首先必須將其字符串轉化爲文本節點,如this is number1: {{number1}}這種,咱們必須轉換爲兩個文本節點,一個是this is number1: ,它不須要進行任何處理;另外一個是{{number1}},它須要進行數據綁定,並實現雙向綁定;由於只有轉化爲文本節點,才能使用v-text相似功能實現數據的綁定;而如何進行將字符串文本分割爲不一樣的文本節點呢,那麼,就只能使用正則方式let reg = /\{\{(.+?)\}\}/ig;{{ number }}這種形式數據與普通正常文本分割以後,再分別建立textNode,以下:框架

function parseText(str) {
    let reg = /\{\{(.+?)\}\}/ig;
    let matchs = str.match(reg), match, tokens = [], index, lastIndex = 0;

    while (match = reg.exec(str)) {
        index = match.index
        if (index > lastIndex) {
          tokens.push({
            value: str.slice(lastIndex, index)
          })
        }
        tokens.push({
            value: match[1],
            html: match[0],
            tag: true
        })
        lastIndex = index + match[0].length
    }

    return tokens;
}

經過上面parseText方法,能夠將this is number: {{number}}轉化爲以下結果:

圖片描述

轉化爲上圖結果後,就對返回數組進行循環,分別經過建立文本節點;這兒爲了性能優化,先建立文檔碎片,將節點放入文檔碎片中;

// function compileTextNode
// el.wholeText => 'this is number: {{number}}'

let tokens = parseText(el.wholeText);
var frag = document.createDocumentFragment();
for(let i = 0, _i = tokens.length; i < _i; i++) {
    let token = tokens[i], el = document.createTextNode(token.value)
    frag.appendChild(el);
}

而在最後編譯完成,執行linker時,主要作兩件事,第一是對須要雙向綁定的節點建立directive,第二是將整個文本節點進行替換;怎麼替換呢?如最開始是一個文本節點this is number: {{number}},通過上面處理以後,在frag中實際上是兩個文本節點this is number: {{number}};此時就使用replaceChild方法使用新的節點替換原始的節點;

// compile.js
function compileTextNode(el) {
    let tokens = parseText(el.wholeText);
    var frag = document.createDocumentFragment();
    for(let i = 0, _i = tokens.length; i < _i; i++) {
        let token = tokens[i], el = document.createTextNode(token.value)
        frag.appendChild(el);
    }

    return function(vm) {
        var fragClone = frag.cloneNode(true);
        var childNodes = Array.prototype.slice.call(fragClone.childNodes), token;
        
        // 建立directive
        ......

        // 經過這兒將`THIS IS TEST {{ number }} test` 這種轉化爲三個textNode
        if(tokens.length) {
            replace(el, fragClone);
        }
    }    
}

// util.js
export function replace (target, el) {
  var parent = target.parentNode
  if (parent) {
    parent.replaceChild(el, target)
  }
}

替換後結果以下圖:

圖片描述

通過與最開始圖比較能夠發現,已經將this is number: {{number}} middle {{number2}}轉化爲this is number: number middle number2;只是此時,仍然展現的是變量名稱,如number,number2;那麼,咱們下面應該作的確定就是須要根據咱們初始化時傳入的變量的值,將其進行正確的展現;最終結果確定應該爲this is number: 5 middle 2;即將number替換爲5、將number2替換爲2;那麼,如何實現上述功能呢,咱們上面提過,使用指令(directive)的方式;下面,就開始進行指令的處理;

Directive(指令)

對於每個指令,確定是隔離開的,互相不受影響且有本身的一套處理方式;因此,咱們就使用對象的方式;一個指令就是一個實例化的對象,彼此之間互不影響;以下代碼:

export default function Directive(vm, el, description) {
    this.vm = vm;
    this.el = el;
    this.description = description;
    this.expression = description ? description.value : '';
}

在建立一個指令時,須要傳入三個參數,一個是最開始初始化var vm = new vueImitate(options)時實例化的對象;而el是須要初始化指令的當前元素,如<p v-show="showNode">this is test</p>,須要建立v-show的指令,此時的el就是當前的p標籤;而description主要包含指令的描述信息;主要包含以下:

// 源碼見 './directives/text.js'
var text = {
  bind () {
    this.attr = this.el.nodeType === 3
      ? 'data'
      : 'textContent'
  },
  update (value) {
    this.el[this.attr] = value
  }
}

// 如,'{{number}}'
description = {
    el: el, // 須要建立指令的元素
    def: text, // 對指令的操做方法,包括數據綁定(bind)、數據更新(update),見上面 text
    name: 'text', // 指令名稱
    value: 'number' // 指令對應數據的key
}

經過new Directive(vm, el, description)就建立了一個指令,並初始化一些數據;下面就先經過指令對界面進行數據渲染;全部邏輯就放到了_bind方法中,以下:

// directive.js
Directive.prototype._bind = function() {
    extend(this, this.description.def);
    if(this.bind) {
        this.bind();
    }

    var self = this, watcher = new Watcher(this.vm, this.expression, function() {
        self.update(watcher.value);
    })

    if(this.update) {
        this.update(watcher.value);
    }
}

// util.js
export function extend(to, from) {
    Object.keys(from).forEach((key) => {
        to[key] = from[key];
    })
    return to;
}

方法首先將傳入的指令操做方法合併到this上,方便調用,主要包括上面說的bindupdate等方法;其主要根據指令不一樣,功能不一樣而不一樣定義;全部對應均在./directives/*文件夾下面,包括文本渲染text.js、事件添加bind.js、v-model對應model.js、v-show對應show.js等;經過合併之後,就執行this.bind()方法進行數據初始化綁定;可是,目前爲止,當去看界面時,仍然沒有將number轉化爲5;爲何呢?經過查看代碼:

export default {
  bind () {
    this.attr = this.el.nodeType === 3
      ? 'data'
      : 'textContent'
  },

  update (value) {
    this.el[this.attr] = value
  }
}

bind並無改變節點展現值,而是經過update; 因此,若是調用this.update(123),可發現有以下結果:

圖片描述

其實咱們並非直接固定數值,而是根據初始化時傳入的值動態渲染;可是目前爲止,至少已經完成了界面數據的渲染,只是數據不對而已;
而後,咱們回頭看下編譯過程,咱們須要在編譯過程去實例化指令(directive),並調用其_bind方法,對指令進行初始化處理;

// 見compile.js 'function compileTextNode'
let    _el = childNodes[j], description = {
    el: _el,
    name: 'text',
    value: tokens[j].value,
    def: publicDirectives['text']
}
vm._directives.push(new Directive(vm, _el, description));

// 見compile.js 'function compile'
let newDirectives = this._directives.slice(_dirLength);
for(let i = 0, _i = newDirectives.length; i < _i; i++) {
    newDirectives[i]._bind();
}

上面說了,目前尚未根據傳入的數據進行綁定,下面,就來對數據進行處理;

數據處理

數據處理包括如下幾個方面:

  • 數據雙向綁定
  • 數據變化後,須要通知到ui界面,並自動變化
  • 對於輸入框,使用v-model時,須要將輸入內容反應到對應數據

數據雙向綁定

須要實現雙向綁定,就是在數據變化後可以自動的將對應界面進行更新;那麼,如何監控數據的變化呢?目前有幾種方式,一種是angular的髒檢查方式,就是對用戶因此操做、會致使數據變化的行爲進行攔截,如ng-click$http$timeout等;當用戶進行請求數據、點擊等時,會對全部的數據進行檢查,若是數據變化了,就會觸發對應的處理;而另外一種是vue的實現方式,使用Object.definProperty()方法,對數據添加settergetter;當對數據進行賦值時,會自動觸發setter;就能夠監控數據的變化;主要處理以下, 源碼見這裏

export function Observer(data) {
    this.data = data;
    Object.keys(data).forEach((key) => {
        defineProperty(data, key, data[key]);
    })
}

export function observer(data, vm) {
    if(!data || typeof data !== 'object') {
        return;
    }

    let o = new Observer(data);
    return o;
}

function defineProperty(data, key, val) {
    let _value = data[key];
    let childObj = observer(_value);

    let dep = new Dep(); //生成一個調度中心,管理此字段的全部訂閱者
    Object.defineProperty(data, key, {
        enumerable: true, // 可枚舉
        configurable: false, // 不能再define
        get: function() {
            if (Dep.target) {
                dep.depend();
            }
            return val;
        },
        set: function(value) {
            val = value;
            childObj = observer(value);
            dep.notify();
        }
    })
}

Observer是一個構造函數,主要對傳入的數據進行Object.defineProperty綁定;能夠監控到數據的變化;而在每個Observer中,會初始化一個Dep的稱爲‘調度管理器’的對象,它主要負責保存界面更新的操做和操做的觸發;

界面更新

在經過上面Observer實現數據監控以後,如何通知界面更新呢?這裏使用了‘發佈/訂閱模式’;若是須要對此模式進行更深刻理解,可查看此連接;而每一個數據key都會維護了一個獨立的調度中心Dep;經過在上面defineProperty時建立;而Dep主要保存數據更新後的處理任務及對任務的處理,代碼也很是簡單,就是使用subs保存全部任務,使用addSub添加任務,使用notify處理任務,depend做用會在下面watcher中進行說明:

// Dep.js

let uid = 0;
// 調度中心
export default function Dep() {
    this.id = uid++;
    this.subs = []; //訂閱者數組
    this.target = null; // 有何用處?
}

// 添加任務
Dep.prototype.addSub = function(sub) {
    this.subs.push(sub);
}

// 處理任務
Dep.prototype.notify = function() {
    this.subs.forEach((sub) => {
        if(sub && sub.update && typeof sub.update === 'function') {
            sub.update();
        }
    })
}

Dep.prototype.depend = function() {
    Dep.target.addDep(this);
}

那麼,處理任務來源哪兒呢?vue中又維護了一個watcher的對象,主要是對任務的初始化和收集處理;也就是一個watcher就是一個任務;而整個watcher代碼以下, 線上源碼見這裏

export default function Watcher(vm, expression, cb) {
    this.cb = cb;
    this.vm = vm;
    this.expression = expression;
    this.depIds = {};

    if (typeof expression === 'function') {
        this.getter = expOrFn;
    } else {
        this.getter = this.parseGetter(expression);
    }

    this.value = this.get();
}

let _prototype = Watcher.prototype;

_prototype.update = function() {
    this.run();
}

_prototype.run = function() {
    let newValue = this.get(), oldValue = this.value;
    if(newValue != oldValue) {
        this.value = newValue;
        this.cb.call(this.vm, newValue);
    }
}

_prototype.addDep = function(dep) {
    // console.log(dep)
    if (!this.depIds.hasOwnProperty(dep.id)) {
        dep.addSub(this);
        this.depIds[dep.id] = dep;
    }
}

_prototype.get = function() {
    Dep.target = this;
    var value = this.getter && this.getter.call(this.vm, this.vm);
    Dep.target = null;
    return value;
}

_prototype.parseGetter = function(exp) {
    if (/[^\w.$]/.test(exp)) return; 

    var exps = exp.split('.');

    return function(obj) {
        let value = '';
        for (var i = 0, len = exps.length; i < len; i++) {
            if (!obj) return;
            value = obj[exps[i]];
        }
        return value;
    }
}

在初始化watcher時,須要傳入vm(整個項目初始化時實例化的vueImitate對象,由於須要用到裏面的對應數據)、expression(任務對應的數據的key,如上面的‘number’)、cb(一個當數據變化後,界面如何更新的函數,也就是上面directive裏面的update方法);咱們須要實現功能有,第一是每一個任務有個update方法,主要用於在數據變化時,進行調用,即:

// 處理任務
Dep.prototype.notify = function() {
    this.subs.forEach((sub) => {
        if(sub && sub.update && typeof sub.update === 'function') {
            sub.update();
        }
    })
}

第二個是在初始化watcher時,須要將實例化的watcher(任務)放入調度中心depsubs中;如何實現呢?這裏,使用了一些黑科技,流程以下,這兒咱們以expression爲'number'爲例:

一、在初始化watcher時,會去初始化一個獲取數據的方法this.getter就是,可以經過傳入的expression取出對應的值;如經過number取出對應的初始化時的值5;

二、調用this.value = this.get();方法,方法中會去數據源中取值,並將此時的watcher放入Dep.target中備用,並返回取到的值;

// watcher.js
_prototype.get = function() {
    Dep.target = this;
    var value = this.getter && this.getter.call(this.vm, this.vm);
    Dep.target = null;
    return value;
}

三、由於咱們在上面Observer已經對數據進行了Object.defineProperty綁定,因此,當上面2步取值時,會觸發對應的getter,以下, 觸發get函數以後,由於上面2已經初始化Dep.target = this;了,因此會執行dep.depend();,就是上面說的depend函數了:

// Observer.js
let dep = new Dep(); //生成一個調度中心,管理此字段的全部訂閱者
Object.defineProperty(data, key, {
    enumerable: true, // 可枚舉
    configurable: false, // 不能再define
    get: function() {
        if (Dep.target) {
            dep.depend();
        }
        return val;
    },
    set: function(value) {
        val = value;
        childObj = observer(value);
        dep.notify();
    }
})

三、觸發dep.depend();以後,以下代碼,會執行Dep.target.addDep(this);, 此時的this就是上面實例化的dep, Dep.target則對應的是剛剛1步中實例化的watcher,即執行watcher.addDep(dep);

// Dep.js
Dep.prototype.depend = function() {
    Dep.target.addDep(this);
}

四、觸發watcher.addDep(dep),以下代碼,若是目前還沒此dep;就執行dep.addSub(this);,此時的this就是指代當前watcher,也就是1步時實例化的watcher;此時dep是步驟3中實例化的dep; 便是,dep.addSub(watcher);

// watcher.js
_prototype.addDep = function(dep) {
    // console.log(dep)
    if (!this.depIds.hasOwnProperty(dep.id)) {
        dep.addSub(this);
        this.depIds[dep.id] = dep;
    }
}

五、最後執行dep.addSub(watcher);,以下代碼,到這兒,就將初始化的watcher添加到了調度中心的數組中;

// Dep.js
Dep.prototype.addSub = function(sub) {
    this.subs.push(sub);
}

那麼,在哪兒去初始化watcher呢?就是在對指令進行_bind()時,以下代碼,在執行_bind時,會實例化Watcher; 在第三個參數的回調函數裏執行self.update(watcher.value);,也就是當監控到數據變化,會執行對應的update方法進行更新;

// directive.js
Directive.prototype._bind = function() {
    extend(this, this.description.def);
    if(this.bind) {
        this.bind();
    }
    var self = this, 
    watcher = new Watcher(this.vm, this.expression, function() {
        self.update(watcher.value);
    })
    if(this.update) {
        this.update(watcher.value);
    }
}

而前面說了,開始時沒有數據,使用this.update(123)會將界面對應number更新爲123,當時沒有對應number真實數據;而此時,在watcher中,獲取到了對應數據並保存到value中,所以,就執行this.update(watcher.value);,此時就能夠將真實數據與界面進行綁定,而且當數據變化時,界面也會自動進行更新;最終結果以下圖:

爲何全部數據都是undefined呢?咱們能夠經過下面代碼知道, 在實例化watcher時,調用this.value = this.get();時,實際上是經過傳入的key在this.vm中直接取值;可是咱們初始化時,全部值都是經過this.options = options || {}; 放到this.options裏面,因此根本沒法取到:

// watcher.js

_prototype.get = function() {
    Dep.target = this;
    var value = this.getter && this.getter.call(this.vm, this.vm);
    Dep.target = null;
    return value;
}
_prototype.parseGetter = function(exp) {
    if (/[^\w.$]/.test(exp)) return; 

    var exps = exp.split('.');

    return function(obj) {
        let value = '';
        for (var i = 0, len = exps.length; i < len; i++) {
            if (!obj) return;
            value = obj[exps[i]];
        }
        return value;
    }
}

那麼,咱們如何能直接能夠經過諸如this.number取到值呢?只能以下,經過下面extend(this, data);方式,就將數據綁定到了實例化的vueImitate上面;

import { extend } from './util.js';
import { observer } from './Observer.js';
import Compile from './compile.js';

export default function vueImitate(options) {
    this.options = options || {};
    this.selector = options.el ? ('#' + options.el) : 'body';
    this.data = typeof options.data === 'function' ? options.data() : options.data;
    this.el = document.querySelectorAll(this.selector)[0];

    this._directives = [];

    this.initData();
    this.compile();
}

Compile(vueImitate);

vueImitate.prototype.initData = function() {
    let data = this.data, self = this;

    extend(this, data);

    observer(this.data);
}

處理後結果以下:

圖片描述

數據也綁定上了,可是當咱們嘗試使用下面方式對數據進行改變時,發現並無自動更新到界面,界面數據並無變化;

methods: {
    add() {
        this.number1 += 1;
        this.number += 1;
    }
}

爲何呢?經過上面代碼可知,咱們其實observer的是vueImitate實例化對象的data對象;而咱們更改值是經過this.number += 1;實現的;其實並無改vueImitate.data.number的值,而是改vueImitate.number的值,因此也就不會觸發observer裏面的setter;也不會去觸發對應的watcher裏面的update;那如何處理呢?咱們能夠經過以下方式實現, 完整源碼見這裏

// init.js
vueImitate.prototype.initData = function() {
    let data = this.data, self = this;

    extend(this, data);

    Object.keys(data).forEach((key) => {
        Object.defineProperty(self, key, {
            set: function(newVal) {
                self.data[key] = newVal;
            },
            get: function() {
                return self.data[key];
            }
        })
    })
        
    observer(this.data);
}

這裏經過對vueImitate裏對應的data的屬性進行Object.defineProperty處理,當對其進行賦值時,會再將其值賦值到vueImitate.data對應的屬性上面,那樣,就會去觸發observer(this.data);裏面的setter,從而去更新界面數據;

至此,整個數據處理就已經完成,總結一下:

一、首先,在初始化vueImitate時,咱們會將初始化數據經過options.data傳入,後會進行處理,保存至this.data中;

二、經過initData方法將數據綁定到vueImitate實例化對象上面,並對其進行數據監控,而後使用observerthis.data進行監控,在實例化Observer時,會去實例化一個對應的調度中心Dep

三、在編譯過程當中,會建立指令,經過指令實現每一個須要處理節點的數據處理和雙向綁定;

四、在指令_bind()時,會去實例化對應的watcher,建立一個任務,主要實現數據獲取、數據變化時,對應界面更新(也就是更新函數的調用)、並將生成的watcher存儲到對應的步驟2中實例化的調度中心中;

五、當數據更新時,會觸發對應的setter,而後調用dep.notify();觸發調度中心中全部任務的更新,即執行全部的watcher.update,從而實現對應界面的更新;

到目前爲止,整個框架的實現基本已經完成。其中包括compile、linker、oberver、directive(v-model、v-show、v-bind、v-text)、watcher;若是須要更深刻的研究,可見項目代碼; 能夠本身clone下來,運行起來;文中有些可能思考不夠充分,忘見諒,也歡迎你們指正;

相關文章
相關標籤/搜索