模板解析
基本流程
1、将el的所有子节点取出,添加到一个新建的文档fragment对象中
function MVVM (options) {
this.$options = options
var data = this._data = this.$options.data
var me = this
// 创建一个编译对象
this.$compile = new Compile(options.el || document.body, this)
}
function Compile (el, vm) {
this.$vm = vm
this.$el = this.isElement(el) ? el : document.querySelector(el)
if (this.$el) {
this.$fragment = this.node2fragment(this.$el) //取出el元素中所有的子节点保存到一个fragment对象中
this.init()
this.$el.appendChild(this.$fragment)
}
}
Compile.prototype = {
node2fragment: function (el) {
var fragment = document.createDocumentFragment(),
child = this
while (child = el.firstChild) {
fragment.appendChild(child)
}
return fragment
},
}
2、对fragment中的所有层次子节点递归进行编译解析处理
function Compile (el, vm) {
this.$vm = vm
this.$el = this.isElement(el) ? el : document.querySelector(el)
if (this.$el) {
this.$fragment = this.node2fragment(this.$el)
this.init() //编译fragment中所有层次的节点
this.$el.appendChild(this.$fragment)
}
}
Compile.prototype = {
init: function () {
this.compileElement(this.$fragment)
},
compileElement: function (el) {
var childNodes = el.childNodes,
me = this;
[].slice.call(childNodes).forEach(function (node) {
var text = node.textContent,
reg = /\{\{(.*)\}\}/;
if (me.isElementNode(node)) {
me.compile(node)
} else if (me.isTextNode(node) && reg.test(node)) {
me.compileText(node, RegExp.$1)
}
if (node.childNodes && node.childNodes.length) { // 如果当前节点还有子节点,递归调用实现所有层次节点的编译
me.compileElement(node)
}
})
},
}
3、对表达式文本节点进行解析
Compile.prototype = {
init: function () {
this.compileElement(this.$fragment)
},
compileElement: function (el) {
var childNodes = el.childNodes,
me = this;
[].slice.call(childNodes).forEach(function (node) {
var text = node.textContent,
reg = /\{\{(.*)\}\}/; // 小括号匹配花括号里面的内容,例如{{class.no}}匹配class.no,在正则中可以用RegExp.$1取出
if (me.isElementNode(node)) {
me.compile(node)
} else if (me.isTextNode(node) && reg.test(node)) { // 判断节点是否是一个大括号格式的文本节点
me.compileText(node, RegExp.$1) // 编译大括号表达式文本节点
}
if (node.childNodes && node.childNodes.length) {
me.compileElement(node)
}
})
},
isElementNode: function (node) {
return node.nodeType === 1
},
isTextNode: function (node) {
return node.nodeType === 3
},
compileText: function (node, exp) {
updateFn && updateFn(node, this.getVMVal(exp))
},
getVMVal: function (exp) {
var val = this.$vm
exp = exp.split('.')
exp.forEach(function (k, v) {
val = val[k]
})
return val
},
updateFn: function (node, val) {
node.textContent = typeof val === 'undefined' ? '' : val
}
}
4、对元素节点的一般指令属性进行解析
function Compile (el, vm) {
this.$vm = vm
this.$el = this.isElement(el) ? el : document.querySelector(el)
if (this.$el) {
this.$fragment = this.node2fragment(this.$el)
this.init() //编译fragment中所有层次的节点
this.$el.appendChild(this.$fragment)
}
}
Compile.prototype = {
init: function () {
this.compileElement(this.$fragment)
},
compileElement: function (el) {
var childNodes = el.childNodes,
me = this;
[].slice.call(childNodes).forEach(function (node) {
var text = node.textContent,
reg = /\{\{(.*)\}\}/;
if (me.isElementNode(node)) {
me.compile(node) // 解析指令
} else if (me.isTextNode(node) && reg.test(node)) {
me.compileText(node, RegExp.$1)
}
if (node.childNodes && node.childNodes.length) {
me.compileElement(node)
}
})
},
compile: function (node) {
var attrs = node.attributes,
me = this;
[].slice.call(attrs).forEach(function (attr) {
var attrName = attr.name
if (me.isDrection(attrName)) {
var exp = attrName.value,
dir = attrName.slice(2);
if (me.isEventDirection(dir)) {
eventHandler(node, me.$vm, dir, exp)
} else {
compileUtil[dir] && compileUtil[dir](node, me.$vm, exp) //解析普通指令
}
node.removeAttribute(attrName)
}
})
},
}
var compileUtil = {
text: function (node, vm, exp) { this.bind(node, vm, exp, 'text') },
html: function (node, vm, exp) { this.bind(node, vm, exp, 'html') },
class: function (node, vm, exp) { this.bind(node, vm, exp, 'class') },
bind: function (node, vm, exp, dir) {
var updateFn = updater[dir+'Updater']
updateFn && updateFn(node, this.getVMVal(vm, exp))
},
updateFn: function (node, val) {
node.textContent = typeof val === 'undefined' ? '' : val
},
getVMVal: function (vm, exp) {
var val = this.$vm
exp = exp.split('.')
exp.forEach(function (k, v) {
val = val[k]
})
return val
},
}
var updater = {
textUpdater: function (node, val) {
node.textContent = typeof val === 'undefined' ? '' : val;
},
htmlUpdater: function (node, val) {
node.innerHTML = typeof val === 'undefined' ? '' : val;
},
classUpdater: function (node, val) {
var className = node.className
node.className = className ? className + ' ' + val : val
},
textUpdater: function (node, val) {
node.textContent = typeof val === 'undefined' ? '' : val;
},
}
5、事件指令解析
function Compile (el, vm) {
this.$vm = vm
this.$el = this.isElement(el) ? el : document.querySelector(el)
if (this.$el) {
this.$fragment = this.node2fragment(this.$el)
this.init() //编译fragment中所有层次的节点
this.$el.appendChild(this.$fragment)
}
}
Compile.prototype = {
init: function () {
this.compileElement(this.$fragment)
},
compileElement: function (el) {
var childNodes = el.childNodes,
me = this;
[].slice.call(childNodes).forEach(function (node) {
var text = node.textContent,
reg = /\{\{(.*)\}\}/;
if (me.isElementNode(node)) {
me.compile(node) // 解析指令
} else if (me.isTextNode(node) && reg.test(node)) {
me.compileText(node, RegExp.$1)
}
if (node.childNodes && node.childNodes.length) {
me.compileElement(node)
}
})
},
compile: function (node) {
var attrs = node.attributes, // 得到标签的所有属性
me = this;
[].slice.call(attrs).forEach(function (attr) { // 遍历所有属性
var attrName = attr.name // 得到属性名:v-on:click
if (me.isDrection(attrName)) { // 判断是否是指令属性
var exp = attrName.value, // 得到属性值-表达式:show
dir = attrName.slice(2); // 得到指令名:on-click
if (me.isEventDirection(dir)) { // 判断是否是事件指令
eventHandler(node, me.$vm, dir, exp) // 解析事件指令
}
node.removeAttribute(attrName) // 移除指令属性
}
})
},
isDrection: function (attr) {
return attr.indexOf('v-') == 0
},
isEventDirection: function (attrName) {
return attrName.indexOf('on') == 0
},
eventHandler: function (node, vm, dir, exp) {
var eventType = dir.split(':')[1], // 得到事件类型/名:click
fn = vm.$options.methods && vm.$options.methods[exp]; // 从methods中得到表达式所对应的函数(事件回调函数)
if (eventType && fn) {
node.addEventListener(eventType, fn.bind(vm), false) // 给节点绑定指定事件名和回调函数(强制绑定this为vm)的DOM事件监听
}
},
}