<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
let depId = 0
let watcherId = 0
const vm = {
_data: {
name: "盲僧",
actionInfo: '会马氏三角杀吗'
},
computed: {
liQing() {
return '你的' + vm.name + vm.actionInfo
}
}
};
function render() {
document.body.innerHTML = vm.liQing
}
function defineReactive(target, key) {
const dep = new Dep();
Object.defineProperty(target, key, {
get() {
if (Dep.target) {
dep.depend(Dep.target);
}
return vm._data[key];
},
set(newVal) {
vm._data[key] = newVal;
dep.notify();
},
});
}
class Dep {
constructor() {
this.subs = [];
this.id = depId++
}
depend() {
Dep.target.addDep(this)
}
notify() {
this.subs.forEach((watcher) => watcher.update());
}
addWatcher(watcher) {
this.subs.push(watcher)
}
}
const stack = []
Dep.target = null
function pushStack(watcher) {
stack.push(watcher)
Dep.target = watcher
}
function popStack() {
stack.pop()
Dep.target = stack[stack.length - 1]
}
class Watcher {
constructor(vm, effectFn, options = {}) {
this.getter = effectFn;
this.vm = vm;
this.id = watcherId++
this.depsId = new Set()
this.deps = []
this.lazy = options.lazy
this.dirty = this.lazy
this.get();
}
get() {
pushStack(this)
this.value = this.getter();
popStack()
return this.value
}
update() {
if (this.lazy) {
this.dirty = true
}
this.get();
console.log("值改变了-触发了更新");
}
evaluate() {
this.get()
this.dirty = false
}
addDep(dep) {
if (!this.depsId.has(dep.id)) {
this.depsId.add(dep.id)
this.deps.push(dep)
dep.addWatcher(this)
}
}
}
function observer(obj) {
Object.keys(obj).forEach(key => {
defineReactive(vm, key)
})
}
function initComputed() {
const computed = vm.computed
const watchers = vm._computedWatchers = Object.create(null)
for (let key in computed) {
watchers[key] = new Watcher(vm, computed[key], { lazy: true })
Object.defineProperty(vm, key, {
get() {
const watcher = vm._computedWatchers[key]
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.deps.forEach(dep => {
dep.depend()
})
}
return watcher.value
}
})
}
}
observer(vm._data)
initComputed()
new Watcher(vm, render)
setTimeout(() => {
vm.name = '瞎子'
}, 3000)
</script>
</body>
</html>
代码解读
function initComputed() {
const computed = vm.computed
const watchers = vm._computedWatchers = Object.create(null)
for (let key in computed) {
watchers[key] = new Watcher(vm, computed[key], { lazy: true })
Object.defineProperty(vm, key, {
get() {
const watcher = vm._computedWatchers[key]
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.deps.forEach(dep => {
dep.depend()
})
}
return watcher.value
}
})
}
}
以上代码是初始化computed,其函数中可以看出在vm上挂载一个_computedWatchers属性,紧接着循环computed,为每一个计算属性添加一个观察者(Watcher),computed和watcher是一个带有标记的watcher,这块new Watcher时传入了{ lazy: true },该lazy作用有两个,1>. 标记当前watcher是一个computeWatcher, 2> 我们都知道computed有缓存功能,只有在其依赖的属性发生变化时,才去重新计算值,否则直接返回旧值。那么这块在watcher舒适化的时候定义一个变量, watcher.dirty = lazy ,后续只有在watcher.dirty为true的时候去重新计算,为false直接返回旧值。那么在什么时候去改变watcher.dirty的状态呢?
evaluate() {
this.get()
this.dirty = false
}
evaluate这个函数是Watcher内部的函数,他在什么时候调用呢 ,可以看到initComputed函数内部对每个计算属性做了劫持,当我们读取计算属性时,会触发get函数,紧接着拿到当前计算属性的watcher,判断其watcher.dirty属性是不是为true,那么第一次这块肯定是true,那么会调用上边的evaluate函数,在函数内部会去触发get函数,也就是重新去计算计算属性的值并保存在watcher.value中,并将watcher.dirty = false,那么在下次访问计算属性的时候,不会走到watcher.evaluate(),就直接 return watcher.value。到这computed缓存功能就实现了。
其次就是computed的依赖发生改变时怎么通知当前计算属性去更新,并且如果模版中如果使用了计算属性{{computed}},改怎么去通知试图更新呢?
先来说下他们的流程:
-
首先在初始化的时候回去初始化computed也就是上边的initComputed函数
-
template:"<div>{{computed}}</div>"
2.1. 在vue模版变异的时候会将模版生成render函数,然后在去挂载组件时会去调用vm._update(vm._render()), 在这块也会去new Watcher(vm,vm._update(vm._render())),
2.2. 所以我们为什么要用一个stack,一个队列去保存当前watcher,在这是当前页面渲染watcher推到队列中,stack = [渲染watcher],
2.3. 紧接着当编译到 {{computed}} 这块时会触发computed的get方法,
2.4. 这时computed 的 Watcher.dirty = true,所以会去触发 Watcher.evaluate(),
2.5. 然后会调用 watcher.get(), Watcher.dirty = false,并且把当前的computedWatcher推到站中,stack = [渲染watcher,计算属性watcher]
2.6. watcher.get()执行完,将watcher.value = this.getter(),也就是计算属性的调用结果 return this.a + this.b,保存起来,并且computedWatcher出栈,然后回到computed的get方法中,stack = [渲染watcher]
2.7. if (Dep.target) 此时的Dep.target就是还在stack中的渲染watcher,那么当前计算属性的中依赖的属性也需要去收集当前渲染watcher,将当前watcher也推到属性的dep中,其目的就是在属性改变时去,dep.notify(),通知其依赖的watcher更新试图或者通知其计算属性的watcher重新计算值。
总结:
以上就是computed的实现原理,如有地方不对,还望大佬指点纠正。最后说下大概流程:
首次渲染:
-> 生成渲染new Watcher(vm,vm._update(vm._render()))
-> wathcer.get()
-> 渲染wathcer入栈
-> 模版编译
-> 解析到{{computed}}触发computedWatcher.get()
-> computedWatcher入栈
-> 计算computedWatcher.value并保存起来,并且更新computedWatcher.dirty = false
-> computedWatcher出栈
-> 回到渲染watcher继续解析模版并渲染视图
当计算属性中依赖值改变时:
-> 依赖值改变会触发依赖的set()
-> 通知其所有观察者dep.notity() -(watcher更新)
-> 调用watcher.update(),里边会判断如果是computeWatcher时,会将当前的computedWatcher.dirty = true,所以在调用渲染atcher.update()时,又会走上边的模版编译....