模板解析的基本流程
1)将el的所有子节点取出,添加到一个新建的文档fragment对象中
2)对fragment 中的所有层次子节点递归进行编译解析处理
querySelector() 方法返回文档中匹配指定 CSS 选择器的一个元素。
指令 = > 操作标签
1、init()是进行编译的
function Compile(el, vm) {
this.$vm = vm; // 保存传来的vm的实例
this.$el = this.isElementNode(el) ? el : document.querySelector(el); // 前者判断是否为元素节点 后者 5.通过querySelector(el) 去查找 存储的一定是Dom
if (this.$el) { // 如果有el
this.$fragment = this.node2Fragment(this.$el); // 1.将el元素的所有子节点保存到fragment
this.init(); // 2.编译fragment中所有层次子节点
this.$el.appendChild(this.$fragment); // 3.将编译好的fragment添加到el中
}
}
2、真正编译是compileElement
init: function() {
this.compileElement(this.$fragment); // 编译指定元素(所有层次的子节点)
}
3、去便利每个子节点(递归调用)
compileElement: function(el) {
var childNodes = el.childNodes, // 取出最外层的子节点
me = this; // 保存compile实例
[].slice.call(childNodes).forEach(function(node) { // 便利所有子节点(text/element)
var text = node.textContent; // 得到节点的文本内容
var reg = /\{\{(.*)\}\}/; // 匹配大括号表达式 {{ name }} () 小括号子匹配 是否取出name
if (me.isElementNode(node)) { // 判断节点是否为一个元素节点
me.compile(node); // 用来编译元素节点属性 所有的指令的(只要是元素节点就编译)
} else if (me.isTextNode(node) && reg.test(text)) { // 判断节点是否是大括号表达式的文本杰点
me.compileText(node, RegExp.$1.trim()); // 编译大括号表达式文本节点
}
if (node.childNodes && node.childNodes.length) { // 子节点是否有子节点么?并且子节点的子节点长度是否大于0?
me.compileElement(node); // 递归调用 实现所有层次节点的编译
}
});
}
4、compileElement调用 绑定名text
compileText: function(node, exp) {
compileUtil.text(node, this.$vm, exp); // 19.编译工具对象 这个方法用来帮助我们编译我们编译v-text 和大括号表达式使用同一函数 exp表达式 $vm vm
}
5、compileUtil 根据传来的指令名选择绑定
var compileUtil = {
// v-text {}
text: function(node, vm, exp) {
this.bind(node, vm, exp, 'text'); // 20.绑定数据 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) {
var updaterFn = updater[dir + 'Updater']; // 得到更新节点的函数
updaterFn && updaterFn(node, this._getVMVal(vm, exp)); // 调用函数更新节点
new Watcher(vm, exp, function(value, oldValue) { // 为更新做准备工作
updaterFn && updaterFn(node, value, oldValue);
});
}
6、_getVMVal 为取值
// 从vm中拿到所对应的值
_getVMVal: function(vm, exp) { // 23.怕出现多个属性
var val = vm; // 去到name所对应的值
exp = exp.split('.'); // 表达式拆分
exp.forEach(function(k) { // 一层一层取
val = val[k];
});
return val;
}
7、通过updaterFn选择方法并赋值
var updater = { // 更新节点的textContent 值
textUpdater: function(node, value) {
node.textContent = typeof value == 'undefined' ? '' : value; // 操作节点的text 24.最后赋值
},
htmlUpdater: function(node, value) {
node.innerHTML = typeof value == 'undefined' ? '' : value; // 操作节点的innerHtml
},
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; // 操作节点的class
},
modelUpdater: function(node, value, oldValue) {
node.value = typeof value == 'undefined' ? '' : value; // 操作节点的value
}
}