The last study note talked about the principle of Vue’s responsive data . We know that Vue completes the data’s responsiveness by Object.defineProperty
performing andget
on the data. From the source code, we know that each object will have its own instance when performing responsive processing. When , will call to collect dependencies, and execute to update dependencies when , this study note will introduce the main process of collecting dependencies;set
dep
get
dep.depend
set
dep.notify
1. Rely on collection
We can first look at the main content of Dep
// src/core/observer/dep.js
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
// 用来收集依赖,里面存放 watcher 实例
this.subs = []
}
// 添加依赖
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 移除依赖
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
// 如果当前全局存在依赖的 watcher,则添加到 subs
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
const subs = this.subs.slice()
// 通知依赖
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// Dep.target 是全局唯一的 watcher 指向
Dep.target = null
// 模拟栈结构来存放 watcher
const targetStack = []
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
From the above source code, we can see Dep
that the main function of is to use an array to collect watcher
instances. When we dep.depend
call we can watcher
collect the currently existing subs
, and dep.notify
then traverse when subs
, and notify watcher
the instance to call update
update in turn. How is Watcher defined? Woolen cloth? Go on down:
// src/core/observer/watcher.js
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid
this.active = true
this.dirty = this.lazy // 计算属性时使用
this.deps = [] // 存放 dep
this.newDeps = []
this.depIds = new Set() // 去重
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
// 传进来的如果是对象中的某个值,如 form.name,Vue 会将字符串转化为实例对应的值
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${
expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
// 如果不是计算属性,则调用一次 get 方法,获取并保存一次旧值
this.value = this.lazy
? undefined
: this.get()
}
get () {
// 将当前的 watcher 实例挂到 Dep.target
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${
this.expression}"`)
} else {
throw e
}
} finally {
if (this.deep) {
// 如果 deep 属性为真,则递归对象的嵌套属性,进行深度依赖收集
traverse(value)
}
// 将当前的 watcher 从 Dep.target 中移除
popTarget()
this.cleanupDeps()
}
return value
}
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
// 将当前实例化的 watcher 添加到 dep 的 subs 中
dep.addSub(this)
}
}
}
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
// 更新渲染 watcher
update () {
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
run () {
if (this.active) {
// 获取新的值
const value = this.get()
if (
value !== this.value ||
isObject(value) ||
this.deep
) {
// 旧的值
const oldValue = this.value
this.value = value
// 如果是用户 watcher,执行用户传进来的回调函数,并将新值旧值传出去
if (this.user) {
const info = `callback for watcher "${
this.expression}"`
invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
} else {
// 执行渲染 watcher
this.cb.call(this.vm, value, oldValue)
}
}
}
}
// 计算属性时使用
evaluate () {
this.value = this.get()
this.dirty = false
}
// 计算属性时使用
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
// 销毁 watcher,在 destory 生命钩子函数调用
teardown () {
if (this.active) {
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
From the perspective of Watcher
the class , we mainly use get
the method to obtain new values, and call update
to watcher
. In addition to these, there are other attributes and methods, which will be used when calculating attributes, and will be added later;
Two, listening properties
In the previous article, we know that Vue will call initWatch
to watch
initialize the property when it is initialized. What is done during initialization, we can check it from the source code:
// src/core/instance/state.js
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
// 自定义 watch 的写法可以是数组、对象、函数、字符串
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
// 遍历数组创建 watcher
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
// 创建 watcher 的核心方法
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
// 如果传入的是对象,例如:
/**
watchData: {
handler(nv, ov) {
...
},
deep: true,
immediate: true
}
**/
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
// 如果传入的是字符串,则 handler 为定义好的方法
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
// $watch 原型方法是创建用户自定义 watch 的核心
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
// 如果 handler 是对象,需要调用一次 createWatcher,拿到处理好的参数
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
// 用户自定义选项,包括 deep, immediate等
options = options || {
}
// 用户自定义 watch 标识
options.user = true
// 传入相应的参数,创建 watch 实例
const watcher = new Watcher(vm, expOrFn, cb, options)
// 如果 immediate 为 true,则马上执行一次渲染 watcher
if (options.immediate) {
const info = `callback for immediate watcher "${
watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn () {
watcher.teardown()
}
}
From the source code we can learn several things:
initWatch
It will traverse user-defined oneswatch
and create them one by one. We know thatwatch
custom can be written in arrays, objects, functions, and strings. Compatibility processing will be performed when creating an instance. If it is an array, it will be created by traversing the array;createWatcher
The method is the corewatch
of , mainly tohandler
judge and process , and thenvm.$watch
create an instance;$watch
The prototype method is the corewatch
of , it willoptions
mark theuser: true
, indicating that it is user-definedwatch
, and thennew Watcher
create an instance, if setimmediate
, execute the callback function passed in by the user once;
Summarize:
- When Vue responds to data processing, it creates an instance
dep
of to collect dependencies. When the data changes, it isdep.notify
notified by callingwatcher
to update.watcher
The main method of updating isget
andupdate
,get
through to get new values, throughupdate
to perform renderingwatcher
; - Vue will execute it when it is initialized
initWatch
,watch
perform , andcreateWacther
create it through the methodwatch
,createWacther
which will use the processedvm.$watch
parameters to createwatch
an instance through the prototype;