上一篇咱们完成了第一步 (数据劫持),从而完成了对属性的监听,这一篇咱们来完成第二步(模板解析)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)
+ }
复制代码