上一篇咱們完成了第一步 (數據劫持),從而完成了對屬性的監聽,這一篇咱們來完成第二步(模板解析)vue
在開始以前,咱們須要對數據進行一層代理,這樣咱們能夠更簡潔的來調用屬性瀏覽器
// 在 vue 中咱們使用屬性是這樣的
vm.name
// 而不是這樣的
vm._data.name
複製代碼
數據代理其實就至關於將 vm._data 中的屬性作了一層映射, 代理到 vm 上。bash
// mvvm.js
class Mvvm {
constructor(options = {}) {
this.$options = options;
this._data = this.$options.data;
observe(this._data)
// + 號表明新增代碼
+ dataProxy(this, this._data)
}
}
// 代理函數
+ function dataProxy(vm, data) {
+ for(let key in data) {
// vm 代理 vm._data上的屬性
+ Object.defineProperty(vm, key, {
+ configurable: true,
+ get() {
+ return vm._data[key] // 實際返回的仍然是 vm._data的屬性值
+ },
+ set(newVal) {
+ vm._data[key] = newVal // 修改vm._data的屬性值
+ }
+ })
+ }
+ }
複製代碼
打開瀏覽器控制檯app
完成了數據代理,下面開始模板解析mvvm
// mvvm.js
// mvvm構造函數
class Mvvm {
constructor(options = {}) {
...
dataProxy(this, this._data)
// 調用函數
+ new Compile(this.$options.el, this)
}
}
// 模板解析函數
+ function Compile(el, vm) {
// 獲取根節點
vm.$el = document.querySelector(el)
// 建立一個空文檔片斷
let fragment = document.createDocumentFragment();
// 正則 用來匹配插值,即 {{ }} 中的內容
let reg = /\{\{(.*?)\}\}/g;
// 將根節點中的子幾點依次添加到 文檔片斷中
while(child = vm.$el.firstChild) {
// 小知識點:在使用appendChild時
// 若是文檔樹中已經存在了 child,child將從文檔樹中刪除,而後從新插入它的新位置。
// 因此當vm.$el中的節點所有插入到fragment中時,child = null,循環終止
fragment.appendChild(child)
}
// 替換 {{}} 種的內容
function replace(frg) {
frg.childNodes.forEach(node => {
let txt = node.textContent
// 文本節點 而且有{{}}
if(node.nodeType === 3 && reg.test(txt)) {
function replaceTxt() {
node.textContent = txt.replace(reg, (matched, placeholder) => {
console.log(placeholder) // 匹配到的內容 a.b.c
return placeholder.split('.').reduce((val, key) => {
return val[key]; // val = vm.a.b.c
}, vm);
});
};
// 替換
replaceTxt();
}
// 遞歸 替換更層次節點
if(node.childNodes && node.childNodes.length) {
replace(node)
}
})
}
replace(fragment)
// 將文檔片斷插入根節點
vm.$el.appendChild(fragment)
+ }
複製代碼