Appearance
九、computed原理
0、使用方式
js
let vm = new Vue({
el: '#app',
data: {
firstName: '张',
lastName: '三'
},
computed: {
// 内部使用了defineProperty,内部有一个变量dirty
// 第一种: 函数方式-常用的方式
fullName() {
// this.firstName和this.lastName在求值时,会记住当前计算属性的watcher
return this.firstName + this.lastName
},
// 第二种:对象方式-几乎不用
fullName2: {
get() {},
set() {}
}
}
});1、初始化方法
js
// src\state.js
// 实现三大步骤
// 1. 需要watcher
// 2. 需要defineProperty
// 3. 需要dirty
// 初始化计算属性
function initComputed(vm) {
// 获取计算属性对象
let computed = vm.$options.computed;
// 用来存放计算属性的watcher
const watchers = vm._computedWatchers = {};
for (let key in computed) {
// 取出对应的值
const userDef = computed[key];
// 判断是函数还是对象,如果是对象就取get方法,最终得到一个getter函数
const getter = typeof userDef == 'function' ? userDef : userDef.get;
// 给每一个计算属性的对象key加一个watcher进行监听
watchers[key] = new Watcher(vm, getter, () => {}, { lazy: true });
// 给每一个计算属性的对象key加一个数据观测
defineComputed(vm, key, userDef);
}
}
function defineComputed(target, key, userDef) {
const sharePropertyDefinition = {};
if (typeof userDef == 'function') {
sharePropertyDefinition.get = createComputedGetter(key, userDef);
} else {
sharePropertyDefinition.get = createComputedGetter(key, userDef.get);
sharePropertyDefinition.set = userDef.set;
}
Object.defineProperty(target, key, sharePropertyDefinition);
}
// 高阶函数做缓存
function createComputedGetter(key) {
// 包装的计算属性的方法,每次取值会调用此方法
return function() {
// 拿到属性对应的watcher
const watcher = this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
// 默认是脏的
watcher.evaluate();
}
// 还有渲染watcher,也需要收集起来
if (Dep.target) {
watcher.depend();
}
// 默认返回watcher上的值
return watcher.value;
}
}
}2、watcher中添加方法
js
// src\observer\watcher.js
class Watcher {
constructor(vm, exprOrFn, cb, options) {
// ...
this.lazy = options.lazy; // 如果watcher上有lazy属性,说明是一个计算属性
this.dirty = this.lazy; // dirty表示取值时是否执行用户提供的方法
// ...
// 默认会先调用一次get方法,进行取值,将结果保留下来
this.value = this.lazy ? void 0 : this.get();
}
addDep(dep) { }
get() { }
run() { }
update() {
if (this.lazy) {
// 计算属性,页面重新渲染获取最新的值
this.dirty = true;
} else {
// 这里不能每次都调用get方法,get方法会重新渲染页面
queueWatcher(this);
}
}
evaluate() {
this.value = this.get();
// 取过一次值之后,就标识为已经取过值了
this.dirty = false;
}
depend() {
// 计算属性watcher会存储dep
// 通过watcher找到对应的所有dep,也让所有的dep记住这个watcher
let i = this.deps.length;
while (i--) {
// 让dep去存储渲染watcher
this.deps[i].depend();
}
}
}3、dep中添加收集
js
// src\observer\dep.js
// 静态属性
Dep.target = null;
let stack = [];
export function pushTarget(watcher) {
// 保留watcher
Dep.target = watcher;
stack.push(watcher); // 渲染watcher、其他watcher
}
export function popTarget() {
// 将变量删除掉
// Dep.target = null;
stack.pop();
Dep.target = stack[stack.length - 1];
}