Vue核心Object.defineProperty(),自己實(shí)現(xiàn)mvvm——第一步 獲取元素并編譯到頁面中
假設(shè)我們將以下內(nèi)容轉(zhuǎn)化為響應(yīng)
<div id="app">
<input type="text" v-model="author.name">
<div>{{author.name}}</div>
<div>{{author.age}}</div>
<ul>
<li>1</li>
<li>1</li>
</ul>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
author: {
name: "wangmoumou",
age:24
}
},
methods:{
},
computed:{
}
})
</script>
創(chuàng)建自己的main.js并且引入
目錄結(jié)構(gòu)如下

1593480686(1).png
首先寫一個Vue的類,接收參數(shù),判斷el根元素是否存在,如果存在,則編譯模板
// 基類,接收參數(shù)
class Vue{
constructor(options) {
this.$el = options.el;
this.$data = options.data;
//判斷根元素是否存在,存在編譯模板
if (this.$el) {
new Compiler(this.$el, this);//編譯
}
}
}
寫編譯方法,編譯分為元素和文本,元素判斷v-開頭,文本判斷 {{}}
//編譯
class Compiler {
constructor(el, vm) {
//判斷el是否是一個元素,如果不是元素,獲取元素
this.el = this.isElementNode(el) ? el : document.querySelector(el)
//此時拿到當(dāng)前的模板,替換,替換的時候放在內(nèi)存中,在內(nèi)存中替換之后,添加到頁面中,如果拿到一次替換一次,會不斷觸發(fā)瀏覽器回流
//放到內(nèi)存中
this.vm = vm
let fragment = this.node2fragment(this.el)
//把節(jié)點(diǎn)內(nèi)容替換
//用數(shù)據(jù)編譯模板,用 vm中的data
this.compile(fragment)
//把節(jié)點(diǎn)內(nèi)容添加到頁面中
this.el.appendChild(fragment)
}
isElementNode(node) { //判斷是否是元素的方法
return node.nodeType === 1;
}
//把節(jié)點(diǎn)移動到內(nèi)存中
node2fragment(node) {
let fragement = document.createDocumentFragment() //創(chuàng)建文檔碎片
let firstChild
while (firstChild = node.firstChild) {
fragement.appendChild(firstChild)
}
return fragement
}
// 核心編譯方法
compile(node) {
let childNodes = node.childNodes
//拿到子節(jié)點(diǎn)之后判斷是否是元素,偽數(shù)組轉(zhuǎn)數(shù)組
let childNodesArr = [...childNodes]
childNodesArr.forEach(child => {
if (this.isElementNode(child)) {
this.compileElement(child) //編譯元素的,v-開頭
//因?yàn)橹荒玫搅说谝淮?,所以需要把自己傳進(jìn)去,判斷子節(jié)點(diǎn)
this.compile(child) //遞歸
} else {
this.compileText(child) //編譯文本的 ,{{}}
}
})
}
// 編譯元素
compileElement(node) {
let attributes = node.attributes //類數(shù)組
let attributesArr = [...attributes] //轉(zhuǎn)數(shù)組
attributesArr.forEach(attr => { //attr 格式 name=value 找到元素
let { name, value: expr } = attr
if (this.isDirective(name)) {
let [, directiveName] = name.split('-')
CompilerUtil[directiveName](node, expr, this.vm) //調(diào)用工具類 指令有很多,所以寫一個工具類,用來處理不同指令
}
})
}
// 編譯文本
compileText(node) {
let content = node.textContent
if (/\{\{(.+?)\}\}/.test(content)) { //找到文本 {{author.name}}等
CompilerUtil['text'](node,content,this.vm)
}
}
//判斷是不是v-開頭的方法
isDirective(name) {
return name.startsWith('v-')
}
}
指令有很多,所以單獨(dú)寫一個編譯工具,用來處理v-model,v-html等指令,暫時數(shù)據(jù)是對象。
//編譯工具
CompilerUtil = {
getValue(vm, expr) { //根據(jù)表達(dá)式獲取數(shù)據(jù)
return expr.split('.').reduce((data, current) => {
return data[current]
}, vm.$data)
},
model(node, expr, vm) {
let value = this.getValue(vm, expr)
let fn = this.updater['modelUpdater']
fn(node, value)
},
html() {
},
text(node,expr,vm){
let fn = this.updater['textUpdater']
let content = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
return this.getValue(vm,args[1])
})
fn(node,content)
},
updater: {
//把數(shù)據(jù)插入節(jié)點(diǎn)
modelUpdater(node, value) {
node.value = value
},
htmlUpdater() {
},
textUpdater(node,value){
node.textContent = value
}
}
}