一、什么是computed
二、computed的官方实现
三、computed的简单实现
在我们的日常使用中,
computed的返回值一般是依赖某个$data的值,那么就有以下两种情况
- 当
$data的值发生改变时,获取对应的computed时,重新执行并返回最新结果 - 当
$data的值没变时,获取对应的computed时,返回之前的结果
1、实现 computed的准备工作
虽然说computed是一个独立的功能,但是这个功能却依赖 Observer、Dep、Watcher等才能去实现,所以必须先实现 Observer、Dep、Watcher
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就是通过一个lazy的watcher去实现的。对于lazy的watcher,当依赖的数据更改时,只做dirty的更改,把该watcher标记为’脏‘的,后面需要获取computed值的时候,再手动去获取watcher的value
四、总结
computed是一个惰性的watcher