// 我们在使用Vue的时候往往会这样初始化
// let vm = new Vue({
// el: '#app',
// data: {
// man: {
// name: 'chenying',
// age: '23',
// }
// }
// })
/** 不难看出Vue是一个类,接受一个options, 也就是钩子的集合,我们来实现一下这个类
我们会将options中的钩子挂载到Vue的实例上,
这里只挂载了我们需要的$el和$data供后序流程使用, 然后通过new Compiler(this.$el, this)来进行编译
**/
class Vue {
constructor(options) {
this.$el = options.el
this.$data = options.data
if (this.$el) { // 如果元素存在 我们需要编译模板
new Compiler(this.$el, this)
}
}
}
// Compiler类
/**
* 首先会判断当前$el上面的值是节点还是字符串, 如果是字符串的话使用document.querySelector获取真实的DOM
然后调用nodeToFragment()方法递归遍历这个DOM节点, 将它存在文档碎片, 也就是内存中。
最后调用complie()方法对文档碎片进行编译,然后将编译后的文档随便追加到页面中。
我们可以很容易的猜到complie()方法的作用就是用options中的数据替换掉Vue语法中的{ {xx} } 和v-xx等值或者逻辑
*
*/
class Compiler {
constructor(el, vm) {
this.el = this.isElemntNode(el) ? el : document.querySelector(el)
this.vm = vm
// 将当前节点的元素全部丢到内存中(源码是通过ast)
let fragment = this.nodeToFragment(this.el)
// 编译模板,用数据替换
this.compile(fragment)
// 将内存的节点放回到页面中
this.el.appendChild(fragment)
}
// 判断是否是node节点
isElemntNode(node) {
return node.nodeType === 1
}
// 蒋元素丢到文档碎片中
nodeToFragment(node) {
let fragment = document.createDocumentFragment()
let firstChild
while (firstChild = node.firstChild) {
fragment.appendChild(firstChild)
}
return fragment
}
// 将内存中的节点编译
compile(node) {
let childNodes = node.childNodes
const childs = [...childNodes]
childs.forEach(child => {
// 如果是节点就走节点的编译 否则就走文本的编译
if (this.isElemntNode(child)) {
this.compileElement(child)
} else {
this.compileText(child)
}
})
}
// 判断是否是指令 v-xxx
isDirective(attrName) {
return attrName.startsWith('v-')
}
// 编译文本
compileText(node) {
const content = node.textContent
// 正则匹配 {{ xxx }} 的文本节点
if (/\{\{(.+?)\}\}/.test(content)) {
CompilerUnit['text'](node, content, this.vm)
}
}
// 编译节点
compileElement(node) {
this.compile(node) // 如果是节点,节点内部的节点或元素也要编译
const attributes = node.attributes // 获取标签上的属性
// Array.from()、[...xxx]、[].slice.call 等都可以将类数组转化为真实数组
const attrs = [...attributes]
attrs.forEach(attr => {
// attr格式:type="text" v-modal="obj.name"
const { name, value: expr } = attr
if (this.isDirective(name)) {
// name格式: v-model v-htmld
let [, directive] = name.split('-') // 拿到 model html 等
CompilerUnit[directive](node, expr, this.vm) // 不同指令走不同的处理函数
}
})
}
}
const CompilerUnit = {
// 根据 man.name 拿到 $data 里面的name的值
getVal(vm, expr) {
return expr.split('.').reduce((data, current) => {
return data[current]
}, vm.$data)
},
// 指令的处理
model(node, expr, vm) {
const fn = this.updata['modeUpdata']
let value = this.getVal(vm, expr)
fn(node, value)
},
// 文本的处理
text(node, expr, vm) {
const fn = this.updata['textUpdata']
let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getVal(vm, args[1].trim()) // 去除首尾空格 兼容多种写法 {{x}} {{ x }}
})
fn(node, content)
},
// 更新页面数据的方法集合
updata: {
// 更新指令
modeUpdata(node, newValue) {
node.value = newValue
},
// 更新文本
textUpdata(node, newValue) {
node.textContent = newValue
}
}
}
console