介紹
我覺得現在學習前端的人都知道MVVM是什麼意思,現在主流的框架React、Vue都使用的MVVM的原理,今天我們就來實現一下MVVM其中的一小部分Compie指令解析。
- compile就是解析的意思,解析我們使用的模板語法。我這篇文章主要是仿照實現Vue的Compile,
效果
- Vue實現的效果
<div id="app">
<input type="text" v-model="a">
<p>{{ b.c }}</p>
<div v-html="c"></div>
</div>
let vm = new Vue({
el : "#app",
data: {
a: 1,
b: {
c: 1
},
c: "<p>165165</p>"
}
});
複製程式碼
- 我們模擬實現的效果
<div id="app">
<ul>
<input type="text" v-model="b.c">
<li>{{ a }}</li>
<li>
<span>9</span>
</li>
<div v-html="c"></div>
<div v-html="<b>56156</b>"></div>
{{ b.c }}
</ul>
</div>
let vm = new MVVM({
el: "#app",
data: {
a: 1,
b: {
c: 2
},
c: "<p>56165</p>"
}
});
複製程式碼
模擬實現
- 我們自定義一個MVVM的類,來模擬new Vue的過程。
let vm = new MVVM({
el: "#app",
data: {
a: 1,
b: {
c: 2
},
c: "<p>56165</p>"
}
});
複製程式碼
- MVVM
class MVVM {
constructor(options) {
this.$el = options.el; // 掛載點
this.$data = options.data; // 資料
new Compile(this.$el, this.$data); // 解析模板
}
}
複製程式碼
- 解析模板
- 檢視傳入的el是元素還是字串
- 如果是字串,根據class或id查詢元素
- 如果是元素直接使用
- 如果元素存在,將元素加入到記憶體中,解析模板
- 檢視傳入的el是元素還是字串
class Compile {
constructor(el, data) {
this.$el = this.isElement(el) ? el : document.querySelector(el);
this.$data = data;
if (this.$el) {
// 第一步:加入記憶體
let fragment = this.toFragment(this.$el);
// 第二步:解析指令
this.compileDirect(fragment);
// 第三步:加入到DOM中
this.$el.appendChild(fragment);
}
}
}
複製程式碼
檢視是否是元素
isElement(node) {
// 1代表是元素
return node.nodeType === 1;
}
複製程式碼
- 加入到記憶體
toFragment(element) {
// 建立文件碎片
let fragment = document.createDocumentFragment();
let firstChild;
// 逐個獲取第一個元素加入到碎片中,直到文件中沒有子元素
while (firstChild = element.firstChild) {
fragment.appendChild(firstChild);
}
return fragment;
}
複製程式碼
效果
元素都加入到了記憶體中,DOM中沒有子元素。- 解析指令
- 獲取子節點
- 如果子節點是元素,解析元素,遞迴遍歷子元素。
- 如果是文字,解析文字。
compileDirect(el) {
let children = el.childNodes;
[...children].forEach(child => {
if (this.isElement(child)) {
// 解析元素
this.compileNode(child);
// 遍歷子元素
this.compileDirect(child);
} else {
// 解析文字
this.compileText(child);
}
});
}
複製程式碼
2.1 建立工具類
compileUtil = {
// v-text || 文字
text(node, value) {
node.textContent = value;
},
// v-model
model(node, value) {
node.value = value;
},
// v-html
html(node, value) {
node.innerHTML = value;
}
}
複製程式碼
2.2 解析文字
compileText(node) {
// 獲取文字內容,{{ a }}
let text = node.textContent;
if (text) {
// 正則匹配, 是否包含指令,{{ a }}
let value = text.match(/{{([^}])+}}/g);
if (value) {
// 獲取 {{ a }} 裡面的資料: a
value = value[0].replace(/{{([^}]+)}}/g, '$1').trim();
compileUtil["text"](node, this.getValue(value));
}
}
}
複製程式碼
2.2.1 獲取文字指令的值
- 如果為級聯屬性,a.b.c需要一層一層的獲取值
getValue(value) {
value = value.split(".");
// 第一次為 this.$data["a"]
// 第二次為 this.$data["a"]["b"]
// 第三次為 this.$data["a"]["b"]["c"]
return value.reduce((prevRes, next) => {
return prevRes[next];
}, this.$data);
}
複製程式碼
2.3 解析元素屬性指令的值
- 獲取元素的所有屬性
- 如果有遍歷每一個屬性
- 獲取到屬性的名字 如: class、v-model(這裡沒有實現v-bind以及縮寫的形式)
- 根據名字獲取對應處理的函式,獲取對應的值
compileNode(node, data) {
let attrs = [...node.attributes];
if (attrs.length > 0) {
attrs.forEach(attr => {
// v-model → model
// v-text → text
// v-html → html
let type = attr.name.slice(2);
// 如果有對應的函式則處理
compileUtil[type] && compileUtil[type](node, this.getValue(attr.value) || attr.value);
});
}
}
複製程式碼
- 插入到文件中
this.$el.appendChild(fragment);
複製程式碼
總結
- 簡單的實現了一個Compile的過程,知識是實現了一些簡單的指令解析,還沒有實現v-bind、v-on以及它們的縮寫形式:、@。
- 重點再於理解,不只是知道和會用,還要學會靈活的運用以及知道他們簡單的原理並能加以實現。
- 如果有興趣的可以看一下我的另兩篇文章