Skip to content

九、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];
}

Released under the MIT License.