依赖收集是 Vue 响应式原理必不可少的一个实现环节,那要弄清楚依赖收集的原理,我们首先需要明确以下几个概念。
观察者模式
观察者模式是一种实现一对多关系解耦的行为设计模式。
它主要涉及两个角色:观察目标、观察者。
它的特点:观察者要直接订阅观察目标,观察目标一做出通知,观察者就要进行处理。
// 观察者集合
class ObserverList {
constructor() {
this.list = [];
}
add(obj) {
this.list.push(obj);
}
removeAt(index) {
this.list.splice(index, 1);
}
count() {
return this.list.length;
}
get(index) {
if (index < 0 || index >= this.count()) {
return;
}
return this.list[index];
}
indexOf(obj, start = 0) {
let pos = start;
while (pos < this.count()) {
if (this.list[pos] === obj) {
return pos;
}
pos++;
}
return -1;
}
}
// 观察者类
class Observer {
constructor(fn) {
this.update = fn;
}
}
// 观察目标类
class Subject {
constructor() {
this.observers = new ObserverList();
}
addObserver(observer) {
this.observers.add(observer);
}
removeObserver(observer) {
this.observers.removeAt(
this.observers.indexOf(observer)
);
}
notify(context) {
const count = this.observers.count();
for (let i = 0; i < count; ++i) {
this.observers.get(i).update(context);
}
}
}
// 测试例子,在数据A变更时,输出A的新值
const observer = new Observer((newval) => {
console.log(`A的最新值是${newval}`);
})
const subject = new Subject();
subject.addObserver(observer);
// 现在,做出改变的通知
subject.notify('Hello, world');
// A的最新值是Hello, world
依赖收集与观察者模式
我们知道,Vue 实现了当一个数据变更时,视图就进行刷新,而且用到这个数据的其他地方也会同步变更;而且,这个数据必须是在有被依赖的情况下,视图和其他用到数据的地方才会变更。所以,Vue 要能够知道一个数据是否被使用,实现这种机制的技术叫做依赖收集。
Vue 中的依赖收集利用的是观察者模式,当依赖的数据发生变化,观察者就会做出处理。那么谁是观察者?谁是观察目标呢?
- 依赖数据是观察目标
- 视图、计算属性、侦听器这些是观察者
依赖收集实现分析
响应式对象的创建
我们都知道 Vue 实现响应式,是利用了 Object.defineProperty() 这个方法,给每一个属性都添加了 getter 和 setter,但是这一过程是在什么时候完成的呢?是如何给把一个属性都变成响应式的呢?我们一起来看下。
在 new Vue() 后,会调用 this._init() 方法进行一系列的初始化操作,而 _init() 这个方法实际上是定义在 Vue 的原型上的,在 Vue.prototype._init 中,其中调用了一个initState() 方法 (如下),这个方法会初始化 props、methods、data、computed 和watch。下面我们主要分析 initProps 和 initData。
// src/core/instance/state.js
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initProps
initProps 实现如下:
function initProps (vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// root instance props should be converted
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
const hyphenatedKey = hyphenate(key)
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
defineReactive(props, key, value, () => {
if (!isRoot && !isUpdatingChildComponent) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm
)
}
})
} else {
defineReactive(props, key, value)
}
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
props 的初始化主要过程,就是遍历定义的 props 配置。遍历的过程主要做两件事情:一个是调用 defineReactive 方法把每个 prop 对应的值变成响应式,可以通过 vm._props.xxx 访问到定义 props 中对应的属性。
initData
initData实现如下:
// src/core/instance/state.js
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}
data 的初始化主要过程也是做两件事,一个是对定义 data 函数返回对象的遍历,通过 proxy 把每一个值 vm._data.xxx 都代理到 vm.xxx 上;另一个是调用 observe 方法观测整个 data 的变化,把 data 也变成响应式,可以通过 vm._data.xxx 访问到定义 data 返回函数中对应的属性。
observe 的功能就是用来监测数据,实现如下:
// src/core/observer/index.js
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
observe 方法的作用就是给非 VNode 的对象类型数据添加一个 Observer,如果已经添加过则直接返回,否则在满足一定条件下去实例化一个 Observer 对象实例。也就是说,我们执行initData(),是调用了 observe(data, true /* asRootData */),而这个 observe() 实际上是 new Observer(data) 的一个实例。
我们继续看下 Observer 的实现:
// src/core/observer/index.js
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
// __ob__ 指向 Observer 对象,每个被双向绑定的对象元素(数组也是对象)都会有一个__ob__ 属性,而且是单例的
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
// vue/src/core/util/lang.js
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
Observer 的构造函数首先实例化 Dep 对象,接着通过执行 def 函数把自身实例添加到数据对象 value 的 _ob_ 属性上(_ob_ 会指向一个Observer对象,每个被双向绑定的对象元素(数组也是对象)都会有一个_ob_ ,而且是单例的),然后判断传进来的value 是否是数组类型,如果是数组,走this.observeArray(value) 这个方法,如果不是数组,执行 this.walk(value)。可以看到 observeArray 是遍历数组再次调用 observe 方法,而 walk 方法是遍历对象的 key 调用 defineReactive 方法。
以上initProps的时候,我们也看到了 defineReactive() 这个方法, 现在 walk 中也用到了这个方法:
// src/core/observer/index.js
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 先 new 一个 Dep 的实例
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: 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
},
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
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
defineReactive 的功能就是定义一个响应式对象,给对象动态添加 getter 和 setter。
defineReactive 函数最开始初始化 Dep 对象的实例,接着拿到 obj 的属性描述符,然后对子对象递归调用 observe 方法,这样就保证了无论 obj 的结构多复杂,它的所有子属性也能变成响应式的对象,这样我们访问或修改 obj 中一个嵌套较深的属性,也能触发 getter 和 setter。最后利用 Object.defineProperty 去给 obj 的属性 key 添加 getter 和 setter。
总结以上创建响应式对象的过程:
- new Vue() 的时候,调用了 this._init(),_init 定义在 Vue.prototype 上;
- 在 执行 this._init() 时执行了 initState();
- initState() 中执行了 initProps() 和 initData() 来初始化props 和 initData;
- initProps() 是遍历 props 的 key,然后调用 defineReactive(),而 initData() 中调用了 observe() 方法,observe 实际上是给非 VNode 的对象类型数据添加一个 Observer,如果已经添加过则直接返回,否则在满足一定条件下去实例化一个 Observer 对象实例
- new Observer() 时,如果不是数组,执行 this.walk(value),如果是数组,执行this.observeArray()
- this.walk() 是遍历对象的 key 调用 defineReactive() 方法;this.observeArray是遍历数组调用 observe 方法;
- defineReactive() 方法,利用了 Object.defineProperty() 去给 obj 的属性 key 添加 getter 和 setter。
关键概念
创建好响应式对象之后,什么时候会触发 getter 呢?在了解这个问题之前,我们先梳理一下以上过程中涉及到的几个关键概念,initData() 时,调用了observe() 方法,observe() 是一个 Observer 的实例,执行 defineReactive() 时,会创建一个 Dep 的实例,Dep 中 Dep.target 是一个全局的 Watcher,这其中涉及到了 Observer、Dep、Watcher 三个类,我们先了解一下这三个类。
Observer
这个上面提到过了,Observer 这个类只为对象/数组 实例一个 Observer 类的实例,而且就只会实例化一次,并且需要数据是可配置的时候才会实例化 Observer 类实例。 总的来说:
- 将 Observer 类的实例挂载在 _ob_ 属性上,提供后续观测数据使用,以及避免被重复实例化。然后,实例化 Dep 类实例,并且将对象/数组作为 value 属性保存下来
- 如果 value 是个对象,就执行 walk() 过程,遍历对象把每一项数据都变为可观测数据(调用 defineReactive 方法处理)
- 如果 value 是个数组,就执行 observeArray() 过程,递归地对数组元素调用observe(),以便能够对元素还是数组的情况进行处理
总结一下,也就是说 Observer 为所有数据添加监听器 Dep。
Dep
Dep 的实现如下:
// src/core/observer/dep.js
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
// 每个Dep都有唯一的ID
this.id = uid++
// subs用于存放依赖
this.subs = []
}
// 向subs数组添加依赖
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 移除依赖
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Dep 是一个 Class,它定义了一些属性和方法,这里需要特别注意的是它有一个静态属性 target,这是一个全局唯一 Watcher,因为在同一时间只能有一个全局的 Watcher 被计算,另外它的自身属性 subs 也是 Watcher 的数组。
Dep 实际上就是对 Watcher 的一种管理。
Watcher
Watcher 是一个类,接收五个参数,组件实例、观察者函数、回调函数、选项和是否是渲染。
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
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.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 // uid for batching
this.active = true
this.dirty = this.lazy // for lazy 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 = 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
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
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) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
/**
* Add a dependency to this directive.
*/
addDep (dep: Dep) {
const id = dep.id
// 检查newDepIds hash表,如果不存在该 dep.id
if (!this.newDepIds.has(id)) {
// 在newDepIds hash表中添加这个id
this.newDepIds.add(id)
// 并且在 newDeps 数组中添加这个 dep
this.newDeps.push(dep)
// 如果depIds hash表中没有该dep,就执行dep.addSub
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
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
/**
* 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)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
teardown () {
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
其中,addDep() 方法接收一个dep实例作为参数,且用到了构造函数中的四个变量:deps、depIds、newDeps、newDepIds。
这四个变量的作用是:
- deps:缓存上一轮执行观察者函数用到的 dep 实例
- depIds:Hash 表,用于快速查找
- newDeps:存储本轮执行观察者函数用到的 dep 实例
- newDepIds:Hash 表,用于快速查找
执行 watcher.addDep(dep) 后,数据对应的 dep 如果在 newDeps 里不存在,就会加入到 newDeps 里,这是因为一次计算过程数据有可能被多次使用,但是同样的依赖只能收集一次。并且如果在 deps 不存在,表示上一轮计算中,当前 watcher 未依赖过某个数据,那个数据相应的 dep.subs 里也不存在当前 watcher,所以要执行 dep.addSub(this),将当前 watcher 加入到数据的 dep.subs 里。
依赖收集
Vue 中,tempalte 经过编译后,会生成 AST 抽象语法树,和 render 函数。
vue-template-compiler 可用于将 Vue 2.0 模板预编译为渲染函数,以避免 runtime-compilation 的编译开销和 CSP 限制。在大多数情况下,应该将其与一起使用 vue-loader,只有在编写具有非常特定需求的构建工具时,才需要单独使用它。
编译模板字符串并返回已编译的 JavaScript 代码,返回的结果是以下格式的对象:
{
ast: ?ASTElement, // parsed template elements to AST
render: string, // main render function code
staticRenderFns: Array<string>, // render code for static sub trees, if any
errors: Array<string> // template syntax errors, if any
}
编译后的template,在 render 中是以下表现形式:
从上图中,我们可以看到:
- 模板中的所有信息都被 render 函数包含;
- 模板中用到的 data 中的属性,都变成了 JS 变量,比如_vm.msg1、_vm.msg2 等
- 模板中的 v-model v-for v-on 都变成了 JS 逻辑,比如 _vm.change()、_vm.toggle() 等;
在 vue 的 $mount 方法中,会调用 mountComponent(),其中关键的几个操作是:
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
此处会执行 vm._render(),在执行 render() 函数过程中,就会访问用到的 data 中的属性,就势必会触发相关属性的 getter,并且这个过程是惰性收集的(比如,computed 中的 newValue 虽然用到了 data 中的 a,但如果 newValue 没有被调用执行,就不会触发 a 的 getter)。
由于 JavaScript 是单线程,所以虽然有多个观察者函数,但是同一个时刻内,就只会有一个观察者函数在执行,那么此刻正在执行的那个观察者函数,所对应的 Watcher 实例,便会被赋给 Dep.target 这一类变量,从而只要访问 Dep.target 就能知道当前的观察者是谁,即 Dep.target 表示当前观察者 watcher 。
每访问一个 data 中的属性,都会触发 getter 访问器属性,执行 reactiveGetter() 这个方法。 reactiveGetter() 中如果有 Dep.target,会走 dep.depend() 这个方法。因为在访问getter之前,就已经进入了某个 watcher 的上下文了,所以 Watcher 类的实例 watcher 已经准备好了,并且已经调用了watcher.get(),Dep.target是有值的,会走 dep.depend()。
每一个 dep 都是 Dep 的实例,调用 dep.depend() 方法,会执行 Dep.target.addDep(this), 其中 this 指向当前的 dep 实例, Dep.target表示当前的watcher,执行 watcher 的 addDep() 方法。
watcher 的 addDep() 方法,数据对应的 dep 如果在 newDeps 里不存在,就会加入到 newDeps 里,如果在 deps 不存在,表示上一轮计算中,当前 watcher 未依赖过某个数据,那个数据相应的 dep.subs 里也不存在当前 watcher,就执行 dep.addSub(this),执行dep.addSub(this),就会执行 this.subs.push(sub),也就是说把当前的 watcher 订阅到这个数据持有的 dep 的 subs 中,这个目的是为后续数据变化时候能通知到哪些 subs 做准备。
当我们去实例化一个渲染 watcher 的时候,首先进入 watcher 的构造函数逻辑,然后会执行它的 this.get() 方法,进入 get 函数,可以看到,先执行的是 this.getter.call(vm, vm),而this.getter 其实对应的是 new Watcher() 时传入的 updateComponent() 这个方法,也就是 vm._update(vm._render(), hydrating),在执行完这个之后,我们可以看到 get() 方法定义了一个 finally,也就是说,在渲染完成后,会执行 finally 中的步骤。
get () {
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) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
Vue 是数据驱动的,所以每次数据变化都会重新 render,那么 vm._render() 方法又会再次执行,并再次触发数据的 getters,所以 Watcher 在构造函数中会初始化 2 个 Dep 实例数组,newDeps 表示新添加的 Dep 实例数组,而 deps 表示上一次添加的 Dep 实例数组。
在执行 cleanupDeps 函数的时候,会首先遍历 deps,移除对 dep.subs 数组中 Wathcer 的订阅,然后把 newDepIds 和 depIds 交换,newDeps 和 deps 交换,并把 newDepIds 和 newDeps 清空。
总结
经过以上几个步骤,创建响应式对象时,每一个 data 中的属性都实例化了一个 dep,在render() 函数执行时,访问到我们模版中用到的响应式属性的时候,就会触发对应的 getter,调用 dep.depend() 方法,也就会执行 Dep.target.addDep(this),而 Dep.target是一个Watcher 实例,执行 watcher 的 addDep 方法,进而执行 this.subs.push(sub) 把当前的 watcher 订阅到这个数据持有的 dep 的 subs 中。
举个例子: 在执行 render() 函数渲染的过程中,我们的 template 模版中访问到了 data 中的某个属性,比如 data.a,就会在 data.a 的 dep.subs 里加入一个 render@watcher;
假如又访问了计算属性 newValue,计算属性里用到了 data.a,那么就会在 data.a 的 dep.subs里加入了 newValue@watcher。所以 data.a 的 dep.subs 里就有了[render@watcher, newValue@watcher]