compiler
功能
- 负责编译模板,解析指令和插值表达式
- 负责页面的首次渲染
- 当数据变化后重新渲染视图
- 结构
![](https://blogs-pics.oss-cn-shanghai.aliyuncs.com/20200707094325.png)
实现思路
- 根据图示,这个类有两个属性以及一些操作属性的方法
- compiler类的构造函数,传入vue的实例,定义el和vm两个属性,分别记住模板和vue实例,紧接着调用
this.compile
编译模板,这里调用这个方法的目的是,当调用new Compile()
时,会立即开始编译模板
compile
方法里,处理所有文本节点的差值表达式和元素节点的指令
- 首先编译el里的所有子节点
- 判断是文本节点还是元素节点,调用相对应的处理方法
- 这里仅仅处理了el下第一层子节点,还要判断子节点下有没有子节点,进行递归调用
compileText
方法,编译文本节点,处理差值表达式
- 定义正则,匹配类似
{{ msg }}
这种差值表达式
- 获取文本节点里内容
- 如果内容匹配到正则,匹配到的值,就是vue实例中,
data
属性的key
- 有了这个key,就可以通过
this.vm[key]
获取对应的值,最后替换到文本节点里
compileElement
方法,编译元素节点,处理指令
- 遍历所有节点的属性
- 获取节点属性的名称
- 通过
isDirective()
方法判断当前属性是否是指令(v-开头)
- 截取掉v-,只保留功能单词。v-text -> text
- 获取节点属性的value值,这个value也是vue实例
data
属性里的key
- 调用
update
处理指令相关功能
update
- 需要3个参数,节点、key(vue实例
data
属性里的key)、指令去掉v-后的内容
- 通过字符串拼接,获取指令对应的方法,并执行
- 这样做的好处是:以后即使有新的指令需要处理,只需新定义处理指令的方法(xxxUpdater)
代码
class Compiler {
constructor (vm) {
this.el = vm.$el
this.vm = vm
this.compile(this.el)
}
// 编译模板,处理文本节点和元素节点
compile (el) {
// children子元素,要找子节点childNodes
let childNodes = el.childNodes
// 转化伪数组
Array.from(childNodes).forEach(node => {
// 处理文本节点
if (this.isTextNode(node)) {
this.compileText(node)
}
// 处理元素节点
if (this.isElementNode(node)) {
this.compileElement(node)
}
// node节点是否有子节点,如果有,递归调用compile
if (node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
update (node, key, attrName) {
let updateFn = this[attrName + 'Updater']
updateFn && updateFn.call(this, node, this.vm[key], key)
}
// 处理v-text指令
textUpdater (node, value, key) {
node.textContent = value
new Watcher(this.vm, key, newValue => {
node.textContent = newValue
})
}
// 处理v-model
modelUpdater (node, value, key) {
node.value = value
new Watcher(this.vm, key, newValue => {
node.value = newValue
})
// 给表单元素注册事件,实现双向绑定
// node就是input元素
// 触发input事件后,事件处理函数会把文本框的值取出,并重新赋值vm[key] = 'xxx'
// 给vm[key]赋值的时候,会触发响应式机制
// 响应式机制是,当数据发生变化,会重新更新视图
node.addEventListener('input', () => {
this.vm[key] = node.value
})
}
// 编译元素节点,处理指令
compileElement (node) {
// 获取所有属性节点
// 遍历所有属性节点
Array.from(node.attributes).forEach(attr => {
// 判断是否是指令
let attrName = attr.name
if (this.isDirective(attrName)) {
// v-text -> text
attrName = attrName.substr(2)
let key = attr.value
this.update(node, key, attrName)
}
})
}
// 编译文本节点,处理差值表达式
compileText (node) {
// console.dir(node)
let reg = /\{\{(.+?)\}\}/
// 获取文本节点内容
let value = node.textContent
if (reg.test(value)) {
let key = RegExp.$1.trim()
node.textContent = value.replace(reg, this.vm[key])
// 创建watcher对象,当数据改变更新视图
new Watcher(this.vm, key, newValue => {
node.textContent = newValue
})
}
}
// 判断元素属性是否是指令
isDirective (attrName) {
// 判断是否v-开头即可
return attrName.startsWith('v-')
}
// 判断节点是否是文本节点
isTextNode (node) {
// nodeType 3=文本节点 1=元素节点
return node.nodeType === 3
}
// 判断节点是否是元素节点
isElementNode (node) {
return node.nodeType === 1
}
}