这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战
一、将this.$data上的属性代理到vm实例上
所谓的将this.$data挂载到vm实例上,实际上就是说不需要通过vm.$data.属性来获取值,而是直接通过vm.属性获取值。
方法:通过Object.defineProperty将this.$data身上的属性代理到this身上。
class Vue {
constructor(options) {
this.$data = options.data;
// 调用数据劫持的方法
Observe(this.$data);
// 将属性绑定到this身上
Object.keys(this.$data).forEach(key => {
Object.defineProperty(this,key,{
enumerable: true,
configurable: true,
get() {
return this.$data[key];
},
set(newValue) {
this.$data[key] = newValue;
}
})
})
}
}
二、文档碎片的作用
文档碎片是什么?
文档碎片的本质是一片内存空间,文档碎片的存在就是为了防止频繁的对DOM进行操作,造成重绘和重排影响性能,通过将页面中的DOM存储在文档碎片中,对文档碎片进行操作,可以有效的防止频繁的对DOM进行操作。
递归文本节点并使用正则进行替换
function replace(node) {
// 匹配插值表达式的正则
const regMustache = /\{\{\s*(\S+)\s*\}\}/
// 证明当前的node节点是一个文本子节点
if (node.nodeType === 3) {
const text = node.textContent;
// 进行正则提取
const execResult = regMustache.exec(text);
if (execResult) {
const value = execResult[1].split('.').reduce((newObj,k) => newObj[k],vm)
node.textContent = text.replace(regMustache,value)
}
return
}
// 不是文本节点,则投入递归
node.childNodes.forEach(child => replace(child))
}
三、创建Dep类进行依赖收集
下面的Dep类主要实现三个功能:
- 在实例身上定义一个存放订阅者的数组。
- 定义一个向数组中增加订阅者的函数。
- 定义一个通知每一个订阅者watcher的方法。
// 收集watcher订阅者的类
class Dep {
constructor() {
this.subs = [];
}
addSub(watcher) {
this.subs.push(watcher);
}
// 负责通知每个watcher的方法
notify() {
this.subs.forEach(watcher => watcher.update())
}
}
四、创建Watcher类的实例
在replace函数第一次渲染的时候,创建Watcher实例:
function replace(node) {
// 匹配插值表达式的正则
const regMustache = /\{\{\s*(\S+)\s*\}\}/
// 证明当前的node节点是一个文本子节点
if (node.nodeType === 3) {
const text = node.textContent;
// 进行正则提取
const execResult = regMustache.exec(text);
if (execResult) {
const value = execResult[1].split('.').reduce((newObj,k) => newObj[k],vm)
node.textContent = text.replace(regMustache,value)
// 在这里创建watcher
new Watcher(vm,execResult[1],(newValue) => {
node.textContent = text.replace(regMustache,newValue);
})
}
return
}
// 不是文本节点,则投入递归
node.childNodes.forEach(child => replace(child))
}
五、将watcher实例存储到dep.subs数组中
将watcher实例存储到dep.subs数组中主要是需要下面三个方面的配合:
- 编译函数在编译DOM节点的时候需要实例化watcher。
- watcher的构造函数中要有下面三行代码。这三行代码需要和Observer中的get进行配合使用
Dep.target = this;
key.split('.').reduce((newObj,k) => newObj[k],vm);
Dep.target = null;
发挥关键作用的在与第二行代码,看似是在调用,其实是在出发Observe中的getter。
- Observe中的getter
get() {
Dep.target && dep.addSub(Dep.target)
return value;
}