一、定義
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)
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 }
Observer.prototype.walk = function (obj) { let val; for (let key in obj) { if (!obj.hasOwnProperty(key)) return; val = obj[key]; // 遞歸this.convert(key, val); } };
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)鍵 } }); };
上面代碼中,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ù)
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)) };
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;
首先編譯模板
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; } };
上面代碼中在編譯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文件了。
/** * 生成指令 * @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) ); }); };
.name =.el =.vm =.expression =.arg == (!.expression) .bind && ._watcher = (.name === 'prop' ? .vm.$parent : ._update, .update(
exports.bind = function () { };/** * 這個(gè)就是textNode對(duì)應(yīng)的更新函數(shù)啦 */exports.update = function (value) { this.el['nodeValue'] = value; console.log("更新了", value); };
但是,用戶(hù)代碼修改了data怎么辦,下面是watcher的相關(guān)代碼,watcher來(lái)幫你解決這個(gè)問(wè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); };
初始化所有的綁定關(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é)論
分類(lèi): JavaScript
http://www.cnblogs.com/bdbk/p/7220603.html