一、定義

vue的數(shù)據(jù)雙向綁定是基于Object.defineProperty方法,通過(guò)定義data屬性的get和set函數(shù)來(lái)監(jiān)聽(tīng)數(shù)據(jù)對(duì)象的變化,一旦變化,vue利用發(fā)布訂閱模式,通知訂閱者執(zhí)行回調(diào)函數(shù),更新dom。

二、實(shí)現(xiàn)

vue關(guān)于數(shù)據(jù)綁定的生命周期是: 利用options的data屬性初始化vue實(shí)力data---》遞歸的為data中的屬性值添加observer--》編譯html模板--》為每一個(gè){{***}}添加一個(gè)watcher;

var app = new Vue({

  data:{

    message: 'hello world',

    age: 1,

    name: {

      firstname: 'mike',

      lastname: 'tom'

    }

  }

});  

1.初始化data屬性

.$data = options.data || {};

這個(gè)步驟比較簡(jiǎn)單將data屬性?huà)煸诘絭ue實(shí)例上即可。

2.遞歸的為data中的屬性值添加observer,并且添加對(duì)應(yīng)的回調(diào)函數(shù)(initbinding)

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

function Observer(value, type) {    this.value = value;    this.id = ++uid;
    Object.defineProperty(value, '$observer', {
        value: this,
        enumerable: false,
        writable: true,
        configurable: true
    });
    this.walk(value); // dfs為每個(gè)屬性添加ob 
 
}

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

Observer.prototype.walk = function (obj) {
    let val;    for (let key in obj) {        if (!obj.hasOwnProperty(key)) return;

        val = obj[key];        // 遞歸this.convert(key, val);
    }
};

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

Observer.prototype.convert = function (key, val) {
    let ob = this;
    Object.defineProperty(this.value, key, {
        enumerable: true,
        configurable: true,
        get: function () {            if (Observer.emitGet) {
                ob.notify('get', key);
            }            return val;
        },
        set: function (newVal) {            if (newVal === val) return;
            val = newVal;
            ob.notify('set', key, newVal);//這里是關(guān)鍵
        }
    });
};

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

上面代碼中,set函數(shù)中的notify是關(guān)鍵,當(dāng)用戶(hù)代碼修改了data中的某一個(gè)屬性值比如app.$data.age = 2;,那么ob.notify就會(huì)通知observer來(lái)執(zhí)行上面對(duì)應(yīng)的回掉函數(shù)。

綁定回掉函數(shù)

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

exports._updateBindingAt = function (event, path) {
    let pathAry = path.split('.');
    let r = this._rootBinding;    for (let i = 0, l = pathAry.length; i < l; i++) {
        let key = pathAry[i];
        r = r[key];        if (!r) return;
    }
    let subs = r._subs;
    subs.forEach((watcher) => {
        watcher.cb(); // 這里執(zhí)行watcher的回掉函數(shù)
    });
};/**
 * 執(zhí)行本實(shí)例所有子實(shí)例發(fā)生了數(shù)據(jù)變動(dòng)的watcher
 * @private */exports._updateChildrenBindingAt = function () {    if (!this.$children.length) return;    this.$children.forEach((child) => {        if (child.$options.isComponent) return;
        child._updateBindingAt(...arguments);
    });
};/**
 * 就是在這里定于數(shù)據(jù)對(duì)象的變化的
 * @private */exports._initBindings = function () {    this._rootBinding = new Binding();    this.observer.on('set', this._updateBindingAt.bind(this))      
};

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

 

 

3.編譯模板

這個(gè)是數(shù)據(jù)綁定的關(guān)鍵步驟,具體可以分為一下2個(gè)步驟。

A)解析htmlElement節(jié)點(diǎn),這里要dfs所有的dom和上面對(duì)應(yīng)的指令(v-if,v-modal)之類(lèi)的

B)解析文本節(jié)點(diǎn),把文本節(jié)點(diǎn)中的{{***}}解析出來(lái),通過(guò)創(chuàng)建textNode的方法來(lái)解析為真正的HTML文件

在解析的過(guò)程中,會(huì)對(duì)指令和模板添加Directive對(duì)象和Watcher對(duì)象,當(dāng)data對(duì)象的屬性值發(fā)生變化的時(shí)候,調(diào)用watcher的update方法,update方法中保存的是Directive對(duì)象更新dom方法,把在當(dāng)directive對(duì)應(yīng)的textNode的nodeValue變成新的data中的值。比如執(zhí)行app.$data.age = 1;

首先編譯模板

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

exports._compile = function () {    this._compileNode(this.$el);
};/**
 * 渲染節(jié)點(diǎn)
 * @param node {Element}
 * @private */exports._compileElement = function (node) {   
    if (node.hasChildNodes()) {
        Array.from(node.childNodes).forEach(this._compileNode, this);
    }
};/**
 * 渲染文本節(jié)點(diǎn)
 * @param node {Element}
 * @private */exports._compileTextNode = function (node) {
    let tokens = textParser.parse(node.nodeValue); // [{value:'姓名'}, {value: 'name‘,tag: true}]
    if (!tokens) return;

    tokens.forEach((token) => {        if (token.tag) {            // 指令節(jié)點(diǎn)
            let value = token.value;
            let el = document.createTextNode('');
            _.before(el, node);            this._bindDirective('text', value, el);
        } else {            // 普通文本節(jié)點(diǎn)
            let el = document.createTextNode(token.value);
            _.before(el, node);
        }
    });

    _.remove(node);
};

exports._compileNode = function (node) {    switch (node.nodeType) {        // text
        case 1:            this._compileElement(node);            break;        // node
        case 3 :            this._compileTextNode(node);            break;        default:            return;
    }
};

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

上面代碼中在編譯textNode的時(shí)候會(huì)執(zhí)行bindDirctive方法,該方法的作用就是綁定指令,{{***}}其實(shí)也是一條指令,只不過(guò)是一個(gè)特殊的text指令,他會(huì)在本ob對(duì)象的directives屬性上push一個(gè)Directive對(duì)象。Directive對(duì)象本身在構(gòu)造的時(shí)候,在構(gòu)造函數(shù)中會(huì)實(shí)例化Watcher對(duì)象,并且執(zhí)行directive的update方法(該方法就是把當(dāng)前directive對(duì)應(yīng)的dom更新),那么編譯完成后就是對(duì)應(yīng)的html文件了。

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

/**
 * 生成指令
 * @param name {string} 'text' 代表是文本節(jié)點(diǎn)
 * @param value {string} 例如: user.name  是表示式
 * @param node {Element} 指令對(duì)應(yīng)的el
 * @private */exports._bindDirective = function (name, value, node) {
    let descriptors = dirParser.parse(value);
    let dirs = this._directives;
    descriptors.forEach((descriptor) => {
        dirs.push(            new Directive(name, node, this, descriptor)
        );
    });
};

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

.name =.el =.vm =.expression =.arg ==  (!.expression) .bind && 
        ._watcher = 
            
            
            (.name === 'prop' ? .vm.$parent : ._update,  
                       .update(

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

exports.bind = function () {
};/**
 * 這個(gè)就是textNode對(duì)應(yīng)的更新函數(shù)啦 */exports.update = function (value) {    this.el['nodeValue'] = value;
    console.log("更新了", value);
};

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

但是,用戶(hù)代碼修改了data怎么辦,下面是watcher的相關(guān)代碼,watcher來(lái)幫你解決這個(gè)問(wèn)題。

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

/**
 * Watcher構(gòu)造函數(shù)
 * 有什么用呢這個(gè)東西?兩個(gè)用途
 * 1. 當(dāng)指令對(duì)應(yīng)的數(shù)據(jù)發(fā)生改變的時(shí)候, 執(zhí)行更新DOM的update函數(shù)
 * 2. 當(dāng)$watch API對(duì)應(yīng)的數(shù)據(jù)發(fā)生改變的時(shí)候, 執(zhí)行你自己定義的回調(diào)函數(shù)
 * @param vm 
 * @param expression {String} 表達(dá)式, 例如: "user.name"
 * @param cb {Function} 當(dāng)對(duì)應(yīng)的數(shù)據(jù)更新的時(shí)候執(zhí)行的回調(diào)函數(shù)
 * @param ctx {Object} 回調(diào)函數(shù)執(zhí)行上下文
 * @constructor */function Watcher(vm, expression, cb, ctx) {    this.id = ++uid;    this.vm = vm;    this.expression = expression;    this.cb = cb;    this.ctx = ctx || vm;    this.deps = Object.create(null);//deps是指那些嵌套的對(duì)象屬性,比如name.frist 那么該watcher實(shí)例的deps就有2個(gè)屬性name和name.first屬性
    this.initDeps(expression);
}/**
 * @param path {String} 指令表達(dá)式對(duì)應(yīng)的路徑, 例如: "user.name" */Watcher.prototype.initDeps = function (path) {    this.addDep(path);    this.value = this.get();
};/**
   根據(jù)給出的路徑, 去獲取Binding對(duì)象。
 * 如果該Binding對(duì)象不存在,則創(chuàng)建它。
 * 然后把當(dāng)前的watcher對(duì)象添加到binding對(duì)象上,binding對(duì)象的結(jié)構(gòu)和data對(duì)象是一致的,根節(jié)點(diǎn)但是rootBinding,所以根據(jù)path可以找到對(duì)應(yīng)的binding對(duì)象
 * @param path {string} 指令表達(dá)式對(duì)應(yīng)的路徑, 例如"user.name" */Watcher.prototype.addDep = function (path) {
    let vm = this.vm;
    let deps = this.deps;    if (deps[path]) return;
    deps[path] = true;
    let binding = vm._getBindingAt(path) || vm._createBindingAt(path);
    binding._addSub(this);
};

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

初始化所有的綁定關(guān)系之后,就是wather的update了

/**
 * 當(dāng)數(shù)據(jù)發(fā)生更新的時(shí)候, 就是觸發(fā)notify
 * 然后冒泡到頂層的時(shí)候, 就是觸發(fā)updateBindingAt
 * 對(duì)應(yīng)的binding包含的watcher的update方法就會(huì)被觸發(fā)。
 * 就是執(zhí)行watcher的cb回調(diào)。watch在
 * 兩種情況, 如果是$watch調(diào)用的話(huà),那么是你自己定義的回調(diào)函數(shù),開(kāi)始的時(shí)候initBinding已經(jīng)添加了回調(diào)函數(shù)
 * 如果是directive,那么就是directive的_update方法
 * 其實(shí)就是各自對(duì)應(yīng)的更新方法。比如對(duì)應(yīng)文本節(jié)點(diǎn)來(lái)說(shuō), 就是更新nodeValue的值
 */

 

三、結(jié)論

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

 

分類(lèi): JavaScript

http://www.cnblogs.com/bdbk/p/7220603.html