上一篇分析了data的初始化过程以及做的一些操作,这一篇分析依赖收集和派发更新。
依赖收集
先看下getter
的代码
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// 重点看get这部分
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
// ...
})
}
在getter
的逻辑里面,关键的两步是const dep = new Dep()
和dep.depend()
,第一步是实例化一个Dep
实例,第二步就是实现依赖收集。
先看第一步,Dep
的实现,代码路径/src/core/observer/dep.js
Dep
import type Watcher from './watcher'
import { remove } from '../util/index'
let uid = 0
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
// 添加观察者
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 移除观察者
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
// 依赖收集 存在target时添加观察者对象
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
// 派发更新 省略 下面有用到
notify () {
// ...
}
}
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
const targetStack = []
export function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
可以看到,Dep
是对subs
数组的操作,而subs
是watcher
数组,所以Dep
其实是对watchers
的管理。
接着看watcher的逻辑,代码路径/src/core/observer/watcher.js
Watcher
let uid = 0
/**
* A watcher parses an expression, collects dependencies,
* and fires callback when the expression value changes.
* This is used for both the $watch() api and directives.
*/
export default class Watcher {
deps: Array<Dep>;
newDeps: 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)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.computed = !!options.computed
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.computed = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.computed // for computed watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
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
)
}
}
if (this.computed) {
this.value = undefined
this.dep = new Dep()
} else {
this.value = this.get()
}
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
// 入栈 缓存Watcher
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 {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
// 递归对象或数组 触发getter
traverse(value)
}
// 出栈 恢复Watcher
popTarget()
// 清除不需要的依赖 逻辑实现在下面
this.cleanupDeps()
}
return value
}
/**
* Add a dependency to this directive.
*/
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)) {
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
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
时,会执行get
函数,通过pushTarget(this)
将当前Watcher
入栈,然后又执行this.getter.call(vm, vm)
,这里的getter
对应了updateComponent
函数,这里会执行一句关键代码vm._update(vm._render(), hydrating)
,vm._render()
会访问vm
上面的数据,这里就会触发所有数据的getter,完成依赖收集。
触发依赖收集之后,根据上面代码里的注释,vue
还做了三步操作
- 如果存在
deep
属性,说明存在深度依赖关系,采用递归进行依赖收集 - 执行
popTarget()
出栈,恢复watcher
成上一状态 - 完成新订阅,执行
cleanupDeps()
,清除旧订阅,也就是清除旧依赖
派发更新
看下setter
代码
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// shallow为false时 将新值设置成响应式对象
childOb = !shallow && observe(newVal)
// 通知订阅者
dep.notify()
}
值发生变化时,会调用dep
的notify
方法,看下Dep
对notify
的定义
// 派发更新
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
这里调用了每个watcher
的update
方法
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
// 同步时执行
} else if (this.sync) {
this.run()
// 推送到观察者队列
} else {
queueWatcher(this)
}
}
这里关注run
和queueWatcher
run
// run 定义在watcher里面
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
run
的作用就是拿到当前的值,进行新旧值相等、是否对象、是否有deep
等判断,满足其中之一就进行回调,而在通过this.get()
拿值的时候,就会触发执行getter
,这也是响应式数据修改时触发渲染的原因。
queueWatcher
代码路径 src/core/observer/scheduler.js
const queue: Array<Watcher> = []
const activatedChildren: Array<Component> = []
let has: { [key: number]: ?true } = {}
let circular: { [key: number]: number } = {}
let waiting = false
let flushing = false
let index = 0
/**
* Push a watcher into the watcher queue.
* Jobs with duplicate IDs will be skipped unless it's
* pushed when the queue is being flushed.
*/
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
// 保证每个watcher只会被添加一次
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
// 同步
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
// 在下一个tick 异步执行flushSchedulerQueue
nextTick(flushSchedulerQueue)
}
}
}
vue
在派发更新的时候使用了队列,这样做的好处就是每次数据改变时不会马上触发watcher
的回调,会先将watcher
添加到队列里,然后在nextTick
后执行flushSchedulerQueue
flushSchedulerQueue
代码路径 src/core/observer/scheduler.js
/**
* Flush both queues and run the watchers.
*/
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child)
// 2. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
// 队列排序 原因如上注释
queue.sort((a, b) => a.id - b.id)
// do not cache length because more watchers might be pushed
// as we run existing watchers
// 队列遍历
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
watcher.run()
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1
if (circular[id] > MAX_UPDATE_COUNT) {
warn(
'You may have an infinite update loop ' + (
watcher.user
? `in watcher with expression "${watcher.expression}"`
: `in a component render function.`
),
watcher.vm
)
break
}
}
}
// keep copies of post queues before resetting state
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
// call component updated and activated hooks
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
// devtool hook
/* istanbul ignore if */
if (devtools && config.devtools) {
devtools.emit('flush')
}
}
flushSchedulerQueue
在这里做了两件事,队列排序和队列遍历。
队列排序的原因,源码上面也已经有了详细的注释,这里不再赘述。
然后对排序后的队列进行遍历,拿到watcher
执行run
方法,run
已在上面分析。
结语
通过上一篇和这一篇的分析,再加上vue源码本身丰富的注释,基本是理清了响应式2.x版本的过程和原理。vue3.0将使用proxy
来替代,原理理解得差不多,新的实现也很容易触类旁通,后面就期待3.0的到来吧~