vue源碼探究(第四彈)
結(jié)束了上一part的數(shù)據(jù)代理容劳,這一部分主要講講vue的模板解析,感覺這個(gè)有點(diǎn)難理解闸度,而且內(nèi)容有點(diǎn)多竭贩,hhh。
模板解析
廢話不多說莺禁,先從簡單的入手留量。
按照之前的套路,先舉一個(gè)例子??:
<div id="test">
<p>{{name}}</p>
</div>
<script type="text/javascript" src="js/mvvm/compile.js"></script>
<script type="text/javascript" src="js/mvvm/mvvm.js"></script>
<script type="text/javascript" src="js/mvvm/observer.js"></script>
<script type="text/javascript" src="js/mvvm/watcher.js"></script>
<script type="text/javascript">
new MVVM({
el: '#test',
data: {
name: '喵喵喵'
}
})
// 這時(shí)候哟冬,我們的頁面還是渲染出 喵喵喵
</script>
接下來講講內(nèi)部的相關(guān)實(shí)現(xiàn):
我們的MVVM中的構(gòu)造函數(shù)中有什么東西楼熄,可以解析我們的模板呢?
// 創(chuàng)建一個(gè)用來編譯模板的compile對(duì)象
this.$compile = new Compile(options.el || document.body, this)
什么是Compile浩峡?
一行一行注釋著解讀
function Compile(el, vm) {
// 保存vm
this.$vm = vm;
// 保存el元素
this.$el = this.isElementNode(el) ? el : document.querySelector(el);
// 如果el元素存在
if (this.$el) {
// 1. 取出el中所有子節(jié)點(diǎn), 封裝在一個(gè)framgment對(duì)象中
// 這里的node2Fragment 就是將node -> 放入 Fragment中可岂,documentFragment將node進(jìn)行批量處理
this.$fragment = this.node2Fragment(this.$el);
// 2. 編譯fragment中所有層次子節(jié)點(diǎn)
this.init();
// 3. 將fragment添加到el中
this.$el.appendChild(this.$fragment);
}
}
Compile.prototype = {
node2Fragment: function (el) {
var fragment = document.createDocumentFragment(),
child;
// 將原生節(jié)點(diǎn)拷貝到fragment
while (child = el.firstChild) {
fragment.appendChild(child);
}
return fragment;
},
init: function () {
// 編譯fragment
this.compileElement(this.$fragment);
},
compileElement: function (el) {
// 得到所有子節(jié)點(diǎn)
var childNodes = el.childNodes,
// 保存compile對(duì)象
me = this;
// 遍歷所有子節(jié)點(diǎn)
[].slice.call(childNodes).forEach(function (node) {
// 得到節(jié)點(diǎn)的文本內(nèi)容
var text = node.textContent;
// 正則對(duì)象(匹配大括號(hào)表達(dá)式)
var reg = /\{\{(.*)\}\}/; // {{name}}
// 這里提出一個(gè)問題,為什么這里的正則匹配要用/\{\{(.*)\}\}/翰灾,而不是/\{\{.*\}\}/呢缕粹?
// 其實(shí)/\{\{.*\}\}/就可以匹配到{{xxx}},這里加一個(gè)()的意義是稚茅,用于.$1,來取得{{}}中的值平斩,eg:name
// 如果是元素節(jié)點(diǎn)
if (me.isElementNode(node)) {
// 編譯元素節(jié)點(diǎn)的指令屬性
me.compile(node);
// 如果是一個(gè)大括號(hào)表達(dá)式格式的文本節(jié)點(diǎn)
} else if (me.isTextNode(node) && reg.test(text)) {
// 編譯大括號(hào)表達(dá)式格式的文本節(jié)點(diǎn)
me.compileText(node, RegExp.$1); // RegExp.$1: 表達(dá)式 name
}
// 如果子節(jié)點(diǎn)還有子節(jié)點(diǎn)
if (node.childNodes && node.childNodes.length) {
// 遞歸調(diào)用實(shí)現(xiàn)所有層次節(jié)點(diǎn)的編譯
me.compileElement(node);
}
});
},
compile: function (node) {
// 得到所有標(biāo)簽屬性節(jié)點(diǎn)
var nodeAttrs = node.attributes,
me = this;
// 遍歷所有屬性
[].slice.call(nodeAttrs).forEach(function (attr) {
// 得到屬性名: v-on:click
var attrName = attr.name;
// 判斷是否是指令屬性
if (me.isDirective(attrName)) {
// 得到表達(dá)式(屬性值): test
var exp = attr.value;
// 得到指令名: on:click
var dir = attrName.substring(2);
// 事件指令
if (me.isEventDirective(dir)) {
// 解析事件指令
compileUtil.eventHandler(node, me.$vm, exp, dir);
// 普通指令
} else {
// 解析普通指令
compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
}
// 移除指令屬性
node.removeAttribute(attrName);
}
});
},
compileText: function (node, exp) {
// 調(diào)用編譯工具對(duì)象解析
compileUtil.text(node, this.$vm, exp);
},
isDirective: function (attr) {
return attr.indexOf('v-') == 0;
},
isEventDirective: function (dir) {
return dir.indexOf('on') === 0;
},
isElementNode: function (node) {
return node.nodeType == 1;
},
isTextNode: function (node) {
return node.nodeType == 3;
}
};
// 指令處理集合
var compileUtil = {
// 解析: v-text/{{}}
text: function (node, vm, exp) {
this.bind(node, vm, exp, 'text');
},
// 解析: v-html
html: function (node, vm, exp) {
this.bind(node, vm, exp, 'html');
},
// 解析: v-model
model: function (node, vm, exp) {
this.bind(node, vm, exp, 'model');
var me = this,
val = this._getVMVal(vm, exp);
node.addEventListener('input', function (e) {
var newValue = e.target.value;
if (val === newValue) {
return;
}
me._setVMVal(vm, exp, newValue);
val = newValue;
});
},
// 解析: v-class
class: function (node, vm, exp) {
this.bind(node, vm, exp, 'class');
},
// 真正用于解析指令的方法
bind: function (node, vm, exp, dir) {
/*實(shí)現(xiàn)初始化顯示*/
// 根據(jù)指令名(text)得到對(duì)應(yīng)的更新節(jié)點(diǎn)函數(shù)
// 取到一個(gè)object的屬性亚享,有2個(gè)方法,一個(gè)是obj. 一個(gè)是obj[]
// 當(dāng)我們要取得屬性是一個(gè)變量的時(shí)候绘面,使用obj[]
var updaterFn = updater[dir + 'Updater'];
// 如果存在調(diào)用來更新節(jié)點(diǎn)
updaterFn && updaterFn(node, this._getVMVal(vm, exp));
// 創(chuàng)建表達(dá)式對(duì)應(yīng)的watcher對(duì)象
new Watcher(vm, exp, function (value, oldValue) {/*更新界面*/
// 當(dāng)對(duì)應(yīng)的屬性值發(fā)生了變化時(shí), 自動(dòng)調(diào)用, 更新對(duì)應(yīng)的節(jié)點(diǎn)
updaterFn && updaterFn(node, value, oldValue);
});
},
// 事件處理
eventHandler: function (node, vm, exp, dir) {
// 得到事件名/類型: click
var eventType = dir.split(':')[1],
// 根據(jù)表達(dá)式得到事件處理函數(shù)(從methods中): test(){}
fn = vm.$options.methods && vm.$options.methods[exp];
// 如果都存在
if (eventType && fn) {
// 綁定指定事件名和回調(diào)函數(shù)的DOM事件監(jiān)聽, 將回調(diào)函數(shù)中的this強(qiáng)制綁定為vm
node.addEventListener(eventType, fn.bind(vm), false);
}
},
// 得到表達(dá)式對(duì)應(yīng)的value
_getVMVal: function (vm, exp) {
// 這里為什么要forEach呢虹蒋?
// 如果你的exp是a.b.c.c.d呢 就需要forEach 如果只是一層 當(dāng)然不需要遍歷啦
var val = vm._data;
exp = exp.split('.');
exp.forEach(function (k) {
val = val[k];
});
return val;
},
_setVMVal: function (vm, exp, value) {
var val = vm._data;
exp = exp.split('.');
exp.forEach(function (k, i) {
// 非最后一個(gè)key,更新val的值
if (i < exp.length - 1) {
val = val[k];
} else {
val[k] = value;
}
});
}
};
// 包含多個(gè)用于更新節(jié)點(diǎn)方法的對(duì)象
var updater = {
// 更新節(jié)點(diǎn)的textContent
textUpdater: function (node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
},
// 更新節(jié)點(diǎn)的innerHTML
htmlUpdater: function (node, value) {
node.innerHTML = typeof value == 'undefined' ? '' : value;
},
// 更新節(jié)點(diǎn)的className
classUpdater: function (node, value, oldValue) {
var className = node.className;
className = className.replace(oldValue, '').replace(/\s$/, '');
var space = className && String(value) ? ' ' : '';
node.className = className + space + value;
},
// 更新節(jié)點(diǎn)的value
modelUpdater: function (node, value, oldValue) {
node.value = typeof value == 'undefined' ? '' : value;
}
};
最后
未完待續(xù)...
接下來飒货,還有一個(gè)更有趣的東西
下一章繼續(xù)~