⚙️ Vue2 computed 的简单实现

1,619 阅读2分钟

一、什么是computed

📃 vue官方文档有详细的介绍,这里就不多赘述

二、computed的官方实现

📃 vue的github有源码的实现,可以去研究之

三、computed的简单实现

在我们的日常使用中,computed的返回值一般是依赖某个$data的值,那么就有以下两种情况

  • $data的值发生改变时,获取对应的computed时,重新执行并返回最新结果
  • $data的值没变时,获取对应的computed时,返回之前的结果

1、实现 computed的准备工作

虽然说computed是一个独立的功能,但是这个功能却依赖 ObserverDepWatcher等才能去实现,所以必须先实现 ObserverDepWatcher

2、实现 Observer

Observer的功能主要把传入的对象使用Object.defineProperty做一层封装,多次使用observer方法为了确保所有的对象类型值都变为响应式

class Observer{
    constructor(data, vm) {
        this.vm = vm
        this.observer(data)
    }

    observer(data) {
        if (data && typeof data === 'object') {
            Object.keys(data).forEach(key => {
                this.defineReactive(data, key, data[key])
            })
        }
    }

    defineReactive(raw, key, value) {
        this.observer(value)

        Object.defineProperty(raw, key, {
            get() {
                // ...
                return value 
            },
            set(val) {
                if (val === value) return
                this.observer(val)
                value = val
                // ...
            }
        })
    }
}
    
class Vue{
    constructor(options) {
        this.$options = options
        this.$data = typeof options.data === 'function' ? options.data() : options.data
        this.$computed = options.computed

        this.initState()
    }

    initState() {
        if (this.$data) this.initData(this.$data)
        if (this.$computed) this.initComputed(this.$computed)
    }

    initComputed() {
        // ...
    }

    initData(data) {
        new Observer(data, this)
        this.proxyDataToVm(data, this)
    }

    proxyDataToVm(data, vm) {
        Object.keys(data).forEach(key => {
            Object.defineProperty(vm, key, {
                get() {
                    return data[key]
                },
                set(val) {
                    if (val === data[key]) return
                    data[key] = val
                }
            })
        })
    }
}

const vm = window.vm = new Vue({
    data() {
        return {
            count: 0
        }
    },
    computed: {
        plusCount() {
            return this.count + 1
        }
    }
})

3、实现 Watcher

Watcher的作用主要是连通视图数据,当数据改变时通知对应的watcher更新最新值,触发回调更新视图

// Watcher

// TARGET类似 Dep.target,只是使用全局变量去表示
let TARGET = null
class Watcher {
    constructor (vm, expOrFn, cb, options) {
        this.vm = vm
        this.cb = cb
        // expOrFn:string|function
        this.getter = typeof expOrFn === 'function'
            ? expOrFn
            : function() {
                // this:vm
                return this[expOrFn]
            }
        this.lazy = false
            
        if (options) {
            this.lazy = !!options.lazy
        }
        
        this.dirty = this.lazy
        this.value = this.lazy
            ? undefined
            : this.get()
    }
    
    get() {
        TARGET = this
        let value
        const vm = this.vm
        value = this.getter.call(vm, vm)
        TARGET = null
        return value
    }
    
    run() {
        const value = this.get()
        const oldValue = this.value
        this.value = value
        this.cb.call(this.vm, value, oldValue)
    }
    
    update() {
        if (this.lazy) {
            this.dirty = true
        } else {
            Promise.resolve().then(() => {
                this.run()
            })
        }
    }
    
    // 惰性 watcher手动求值
    evaluate() {
        this.value = this.get()
        this.dirty = false
    }
}

4、实现 Dep

Dep是一个收集watcher的容器

// Dep

class Dep {
    constructor() {
        this.subscribes = []
    }

    depend() {
        if (TARGET && !this.subscribes.includes(TARGET)) {
            //TARGET: watcher 
            this.subscribes.push(TARGET)
        }
    }

    notify() {
        this.subscribes.forEach(watcher => {
            watcher.update.call(watcher)
        })
    }
}

5、实现 computed

零部件都有了,剩下的就是简单地拼装,然后实现computed的功能就好了

const noop = () => {}

class Watcher{}

class Dep{}

class Observer{
    constructor(data, vm) {}

    observer(data) {}

    defineReactive(raw, key, value) {
        this.observer(value)

        const dep = new Dep()
        Object.defineProperty(raw, key, {
            get() {
                dep.depend()
                return value 
            },
            set(val) {
                if (val === value) return
                this.observer(val)
                value = val
                dep.notify()
            }
        })
    }
}

class Vue{
    constructor(options) {}

    initState() {}

    initData(data) {}

    proxyDataToVm(data, vm) {}
    
    initComputed() {
        const watchers = this._computedWatchers = Object.create(null)
        const computed = this.$computed
        
        for (const key in computed) {
            const getter = computed[key]
            
            const computedWatcherOptions = {
                lazy: true
            }
            
            // 绑定computed的key到vm上
            if (!(key in this)) {
                watchers[key] = new Watcher(
                    this, //vm
                    getter, //expOrFn
                    noop, // cb
                    computedWatcherOptions //options
                )
            
                this.defineComputed(key)
            } else {
                console.log(`[error]:${key}已经存在`)
            }
        }
    }
    
    defineComputed(key) {
        const computedGetter = {
            get() {
                const watcher = this._computedWatchers && this._computedWatchers[key]
                if (watcher) {
                    if (watcher.dirty) {
                        // 更新 watcher的 value
                        watcher.evaluate()
                    }
                    
                    return watcher.value
                }
            }
        }
    
        Object.defineProperty(this, key, computedGetter)
    }
}

从代码中可以看出,computed就是通过一个lazywatcher去实现的。对于lazywatcher,当依赖的数据更改时,只做dirty的更改,把该watcher标记为’脏‘的,后面需要获取computed值的时候,再手动去获取watchervalue

四、总结

computed是一个惰性的watcher