watcher 是什么
- 当属性变化后会通知自己对应的
watcher去更新(派发更新) - 在update方法中,不同类型会执行不同逻辑。
- 分为三种,渲染watcher、计算watcher、侦听watcher
代码如下
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
dep: Dep;
deps: Array<Dep>;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
this.cb = cb
this.id = ++uid
this.deps = []
this.newDeps = []
}
get () {
//Dep.target = this
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
...
} finally {
popTarget()
this.cleanupDeps()
}
return value
}
// watcher添加到dep
addDep (dep: Dep) {
dep.addSub(this)
this.newDeps.push(dep)
}
cleanupDeps () {
dep.removeSub(this)
this.newDeps = this.deps
}
// 派发更新
update () {
if (this.computed) {
if (this.dep.subs.length === 0) {
this.dirty = true
} else {
this.getAndInvoke(() => {
this.dep.notify()
})
}
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
// queueWatcher维护watcher队列,依次执 watcher.run()
run () {
if (this.active) {
this.getAndInvoke(this.cb)
}
}
getAndInvoke (cb: Function) {
const value = this.get()
cb.call(this.vm, value, oldValue)
}
}
渲染watcher
- 最终会 导致页面视图更新
queueWatcher 会执行queue.push(this) 把当前watcher推入队列,最终执行watcher.run()
getAndInvoke 对于渲染 watcher ,this.cb如下:
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
computed watcher
- 计算属性,返回一个值
- 依赖的属性变化会导致computed重新计算,使用该值(触发该watcher的get方法)才计算
computed: {
reverseMessage: function(value){
console.log(" I'm reverseMessage" )
return this.message.split('').reverse().join('');
}
}
- 为computed对象上每个key添加一个watcher
- 该watcher会持有一个dep属性,dep的subs为依赖该属性的其他watcher,记为 watcher_sub
- get计算属性会触发computedGetter方法
- watcher.depend 会将Dep.target(watcher_sub)添加进this.dep.subs里
- watcher.evaluate会触发 this.dep.subs.notify()即watcher_sub的update方法 源码如下:
const computedWatcherOptions = { computed: true }
function initComputed (vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
}
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
sharedPropertyDefinition.get = createComputedGetter(key)
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
watcher.depend()
return watcher.evaluate()
}
}
}
watch watcher
- 侦听属性,它主要用于侦听数据的变化,在数据发生变化的时候执行一些操作
- 依赖的属性变化会执行watch的回调
watch: {
counter: function(newValue, oldValue){
if(this.counter == 10){
this.counter = 0;
}
}
}
- 为watch对象上每个key添加一个watcher
- new Watcher会执行 this.value=this.get()会获取监测的属性counter,进而将该watcher添加进counter的dep.subs;当改变counter时会执行dep.notify(),进而执行 new Watcher时的cb,达到监测counter变化执行该回调
- 全局方法.$watch(),返回一个销毁watcher的方法
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
createWatcher(vm, key, handler)
}
}
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
...
return vm.$watch(expOrFn, handler, options)
}
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
cb.call(vm, watcher.value)
}
return function unwatchFn () {
watcher.teardown()
}
}
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
) {
this.getter = expOrFn
this.value = this.get()
}
get () {
//Dep.target = this
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
...
} finally {
popTarget()
this.cleanupDeps()
}
return value
}
...
}
欢迎关注我的前端自检清单,我和你一起成长