计算属性原理
- 依赖值发生改变后才会重新执行用户的方法,需要维护一个
dirty
属性,默认不会立即执行
- 计算属性是一个
defineProperty
,有getter
和setter
的方法;也是一个watcher,当计算属性被调用的时候会执行get
方法,从而触发内部调用其他响应式属性的setter
方法,从而使得响应式属性收集计算属性的watcher,这样还不够,内部调用的的响应式属性还需要收集计算属性所处组件的watcher,使得内部响应式属性调用setter
后通知计算属性的watcher
改变dirty
属性,并让组件的watcher重新执行,当组件的render重新执行后又会去调用计算熟悉的get
方法,由于dirty
为true,会重新执行一下其get
方法。
// 需要一个数组,存储当前组件的watcher,然后存储当前计算属性的watcher
Dep.target = null
const stack = []
export function pushTarget(watcher) {
stack.push(watcher)
Dep.target = watcher
}
export function popTarget() {
stack.pop()
Dep.target = stack.length > 0 ? stack[stack.length - 1] : null
}
// watcher
function initComputed(vm) {
const computed = vm.$options.computed;
const watcher = vm._computedWatchers = {}
for (const key in computed) {
let userDef = computed[key]
const fn = typeof userDef === 'function' ? userDef : userDef.get
watcher[key] = new Watcher(vm, fn, { lazy: true })
defineComputed(vm, key, userDef)
}
}
function defineComputed(target, key, userDef) {
const getter = typeof userDef === 'function' ? userDef : userDef.get
const setter = userDef.set || (() => {})
Object.defineProperty(target, key, {
get: createComputedGetter(getter, key),
set: setter
})
}
function createComputedGetter(getter) {
return () => {
const watcher = this._computedWatcher[key]
if (watcher.dirty) { // 计算属性不会收集当前组件的watcher
watcher.evalute()
}
if (Dep.target) { // 让计算属性中的所依赖的属性去收集组件的watcher
watcher.depend()
}
return watcher.value
}
}
class Watcher {
constructor(vm, fn, options) {
...
this.lazy = options.lazy
this.dirty = this.lazy
this.lazy ? undefined : this.get()
}
evalute() {
this.value = this.get()
this.dirty = false
}
update() {
if (this.lazy) {
this.dirty = true
} else {
queueWatcher(this)
}
}
depend() {
let i = this.deps.length
while (i--) { // 这也就是为什么watcher身上需要记住哪些属性收集了自己,目的就是为了computed属性中依赖的属性去收集当前的组件的watcher
this.deps[i].depend()
}
}
}
watch属性原理
- watch也是基于Watcher的,watcher会立即执行,只需要让其立即执行一下监听属性的
get
即可让响应式数据收集当前用户自己的watcher,后续响应式数据发生改变去触发用户自己的callback
function initWatch(vm) {
const watch = this.$options.watch
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; ++i) {
createWatch(vm, key, handler[i])
}
} else {
createWatch(vm, key, handler)
}
}
}
function createWatch(vm, key, handler) {
if (typeof handler === 'string') handler = vm[handler]
return vm.$watch(key, handler)
}
Vue.prototype.$watch = (exprOrFn, cb) => {
new Watch(this, exprOrFn, { user: true }, cb) // 标志自己的watcher
// watch会立即执行,会触发需要监听属性的`get`,从而使得该响应式属性收集用户的watcher
}
// watcher
class Watcher {
constructor(vm, exprOrFn, options, cb) {
if (typeof exprOrFn === 'string') {
this.getter = function() {
return vm[exprOrFn]
}
} else this.getter = exprOrFn
this.user = options.user
this.cb = cb
this.value = this.lazy ? undefined : this.get()
}
run() {
let oldValue = this.value
this.value = this.get()
if (this.user) {
this.cb.call(this.vm, newvalue, oldvalue)
}
}
}