Vue 在创建实例对象时,会将 data
、props
、computed
、watch
设置为 响应式对象
这些过程发生在 initState(vm)
内,它的定义在 src/core/instance/state.js
中
export function initState (vm: Component) {
vm._watchers = []
// 获取 options
const opts = vm.$options
// 处理 props
if (opts.props) initProps(vm, opts.props)
// 处理 methods
if (opts.methods) initMethods(vm, opts.methods)
// 处理 data
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
// 处理 computed
if (opts.computed) initComputed(vm, opts.computed)
// 处理 watch
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
复制代码
initState
函数会依次处理 props
、methods
、data
、computed
、watch
先说一下 data
的响应原理,其他的会分别写一篇文章
data 的响应原理
在initState
函数执行的过程中,如果 opts.data
存在,调用initData
去初始化 data
,将data
变为响应式对象
initData
initData
函数定义在src/core/instance/state.js
中
function initData (vm: Component) {
let data = vm.$options.data
// data 是一个函数的话,先去执行这个函数拿到 返回值并赋值给 vm._data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
// 保证 methods、props、data 中没有同名属性
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
// 如果 methods 中存在 key 报错
if (methods && hasOwn(methods, key)) {
warn(`Method "${key}" has already been defined as a data property.`, vm)
}
}
// 如果 props 中存在 key 报错
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(/* ... */)
} else if (!isReserved(key)) {
// 使开发者可以直接通过 this.xxx 的方式访问 data 中的属性
proxy(vm, `_data`, key)
}
}
// 给 data 添加响应
observe(data, true /* asRootData */)
}
复制代码
initData
总共会做 3 件事:
- 首先会验证
methods
、props
、data
中没有同名属性; - 通过
proxy
将data
中的key
代理到vm
上,从而可以通过this.xxx
的方式访问data
中的属性; - 通过
observe
函数创建观察者实例并给data
所有属性添加响应
proxy
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
// 设置代理,将 key 代理到 target 上
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
复制代码
observe
定义在/src/core/observer/index.js
中
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 非对象和 VNode 实例不做响应式处理
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// 如果 value 对象上存在 __ob__ 属性,说明已经添加响应了,直接返回 __ob__ 属性
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)
}
// 如果当前添加响应的对象是 data,asRootData 是 true
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
复制代码
observe
函数接收 2 个参数
value
要添加响应的对象asRootData
如果添加响应的对象是data
,asRootData
是true
如果shouldObserve
为 true
,并且value
为非 VNode
对象 ,则通过Observer
创建观察者实例,如果对象已经被观察过,返回已有的观察者实例
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
// 创建 Dep 实例
this.dep = new Dep()
this.vmCount = 0
// 将 自身实例 挂载到 value.__ob__ 上
def(value, '__ob__', this)
// 数组的处理逻辑(后面说)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
// 如果 value 是对象
this.walk(value)
}
}
// 遍历所有属性,并为属性添加响应
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* 遍历数组,如果数组元素为对象,则为该对象创建观察者实例,并添加响应
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
复制代码
Observer
在实例化过程中将this
挂载到 value.__ob__
上;也就是说响应式对象都有一个__ob__
属性,指向Observer
实例。还会在this.dep
上挂载一个Dep
实例;
接下来判断是不是数组,如果是数组会走数组的处理逻辑(后面会说);如果是对象则调用walk
遍历对象的每个属性,通过defineReactive
方法给每个属性设置getter
和setter
defineReactive
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 实例化 dep,一个 key 对应一个 dep
const dep = new Dep()
// 获取 obj[key] 的属性描述符,发现它是不可配置对象的话直接 return
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]
}
// 递归调用,处理 val 即 obj[key] 的值为对象的情况,保证对象中的所有 key 都被观察
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// ...
const value = getter ? getter.call(obj) : val
// ...
return value
},
set: function reactiveSetter (newVal) {/* ... */}
})
}
复制代码
defineReactive
函数
- 实例化一个
Dep
对象,一个key
对应一个Dep
实例 - 接着拿到
obj
的属性描述符,判断是否是可配置的; - 对子对象递归调用
observe
方法,这样就保证了无论obj
的结构多复杂,它的所有子属性也能变成响应式的对象,这样访问或修改obj
中一个嵌套较深的属性,也能触发getter
和setter
。 - 利用
Object.defineProperty
去给obj
的属性key
添加getter
和setter
数组响应原理
在 Observer
中有如下逻辑
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
// 如果 value 是对象
this.walk(value)
}
复制代码
在实例化Observer
过程中,会判断value
是不是数组,如果是数组则会先判断当前环境有没有__proto__
属性,如果有则执行protoAugment
反之执行copyAugment
// can we use __proto__?
export const hasProto = '__proto__' in {}
复制代码
在看protoAugment
和copyAugment
之前先看下 arrayMethods
arrayMethods
定义在src/core/observer/array.js
中
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) {
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
// 方法添加的元素
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2) // splice 方法第二个参数之后都是新增元素
break
}
// 对新增元素设置响应
if (inserted) ob.observeArray(inserted)
// 手动触发依赖通知
ob.dep.notify()
return result
})
})
复制代码
Vue 会重写数组的一些方法,将重写后的方法,挂载到arrayMethods
上。
重写思路如下:
- 当调用数组方法时,会先执行原生方法,获取原生函数的返回值
reslut
; - 如果是新增方法,则对新增的元素添加响应,前提是新增元素是对象
- 触发当前数组的
dep.notify
通知Watcher
更新 - 返回原生函数的返回值
reslut
知道了arrayMethods
的内容后,看下protoAugment
和copyAugment
,这俩函数比较简单就不展示代码了,直接说干了啥
protoAugment
将arrayMethods
赋值给了数组的__proto__
属性copyAugment
遍历arrayKeys
(arrayMethods
的属性名集合),将arrayMethods
所有属性都挂载到了数组上
也就是说当被监听的对象如果是数组,Vue 会重写数组的一些方法,并将这些重写后的方法,挂载到数组上。当调用这些方法时,会先执行原方法并获取返回值,如果是新增属性,则对新增属性添加响应;最后手动触发更新。
需要注意的点是这里的dep
是数组的Dep
,在实例化Observer
过程中,也会创建一个Dep
实例
添加响应的流程图如下
依赖收集
在看 getter
方法前,先看下前面说的 Dep
和 Watcher
Dep
import type Watcher from './watcher'
import { remove } from '../util/index'
import config from '../config'
let uid = 0
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
// 初始化 subs 数组,用于存储依赖此属性的 Watcher
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Dep.target = null
const targetStack = []
// 修改 Dep.target 的值,此时 Dep.target 的值 就是当前正在更新的 Watcher(入栈)
// 并将当前正在更新的 Watcher 添加到 targetStack 中
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
// 删除 targetStack 最后一个元素,并修改 Dep.target 的值(出栈)
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
复制代码
Dep
是一个 Class,它定义了一些属性和方法
Dep.target
:这是一个全局唯一Watcher
,在同一时间只能有一个全局的Watcher
被计算this.subs
:初始化subs
数组,用于存储依赖此属性的Watcher
addSub
:将依赖此属性的Watcher
添加到subs
中,做依赖收集removeSub
:删除subs
中的Watcher
notify
:遍历subs
中所有元素,并执行元素的update
方法,去通知Watcher
更新
Watcher
Watcher
实例化过程比较复杂,在依赖收集的流程中会具体说一下
依赖收集的流程
先从挂载开始说,$mount
会调用mountComponent
函数去创建 一个渲染Watcher,并传入updateComponent
函数,也就是class Watcher
的第二个参数expOrFn
// ...
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
// ...
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher 这里将是 true */)
复制代码
看下 Watcher
的定义
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
// 对于渲染 Watcher而言,isRenderWatcher 为 true
if (isRenderWatcher) {
// 只有渲染 Watcher 才会将自身挂载到 vm._watcher 上
vm._watcher = this
}
vm._watchers.push(this)
if (options) {
// 初始化 Watcher 的 属性,对于渲染 Watcher 来说 都是 false
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
// 渲染 Watcher 的 before 函数会执行 beforeUpdate 生命周期
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 = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// 渲染 Watcher 的 expOrFn 就是 updateComponent,是一个函数
if (typeof expOrFn === 'function') {
// 将 updateComponent 赋值给 getter 属性
this.getter = expOrFn
} else {}
// 渲染 Watcher 的 lazy 属性为 false,所以在初始化过程中会执行 get 方法
this.value = this.lazy
? undefined
: this.get()
}
get () {}
addDep (dep: Dep) {}
cleanupDeps () {}
update () {}
run () {}
evaluate () {}
depend () {}
teardown () {}
}
复制代码
初始化 渲染Watcher 的时候,需要注意以下几个点
vm._watcher = this
将自身挂载到vm._watcher
上- 初始化四个属性
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
复制代码
- 将
updateComponent
赋值给getter
属性 - 执行
this.get()
方法
看下 get
方法
get () {
// 将 Dep.target 赋值成当前 Watcher
pushTarget(this)
let value
const vm = this.vm
try {
// 执行 getter 方法,也就是执行 updateComponent 方法
value = this.getter.call(vm, vm)
} catch (e) {
} finally {
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
复制代码
首先调用pushTarget
将Dep.target
指向当前 渲染Watcher,调用this.getter
从而执行_render
方法,在执行_render
方法的过程中,会获取响应式属性的属性值,从而触发属性的getter
方法做依赖收集
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
// Object.getOwnPropertyDescriptor 返回对象上一个自有属性对应的属性描述符
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
// 这里添加判断的可能性
// 1. 修改属性值的时候,可能会获取其他属性的属性值,这里这样做可以防止再次进行依赖收集
// 2. 当执行生命周期函数的时候,防止进行依赖收集
if (Dep.target) {
dep.depend()
if (childOb) {
// 这样做的目的是
// 1. 当执行数组的某些方法时,可以更新视图
// 因为调用数组的某些方法其实会手动触发更新,但是更新的是数组 dep 里面存放的Watcher,所以在依赖收集过程中,需要将这个Watcher存放到数组的Dep实例里面
// 2. 通过 $set 给对象添加属性时,可以更新视图(原因和第一条相同)
childOb.dep.depend()
if (Array.isArray(value)) {
// 如果是数组并且数组元素中有对象,将这个 Watcher 添加到对象的 dep.subs 中
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {/* ... */}
})
}
复制代码
getter
首先判断Dep.target
是否存在,此时Dep.target
指向当前渲染Watcher,接着执行dep.depend()
,也就是执行Watcher的addDep
方法
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)
}
}
}
复制代码
这时候会做一些逻辑判断(保证同一数据不会被添加多次)后执行 dep.addSub(this)
,那么就会执行 this.subs.push(sub)
,也就是说把当前Watcher
添加到这个数据持有的 dep
的 subs
中,这个目的是为后续数据变化时候能通知到哪些 subs
做准备
回到 getter
方法,如果 childOb
是一个Observer
实例,则将当前 Watcher 添加到 childOb.dep
的 subs
中;判断 value
是不是数组,如果是则执行dependArray
,dependArray
作用是如果数组中有对象,则将 Watcher 添加到对象的dep.subs
中。
依赖收集完成之后,回到 Watcher 的 get
方法中,执行popTarget()
恢复 Dep.target
的值,然后执行cleanupDeps
清空过程;比对 newDeps
和 deps
,如果 deps
有而 newDeps
没有,说明新的VNode已经不依赖当前属性了,则将当前watcher从这个 dep
的subs
中删除
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
}
复制代码
依赖收集完成后,VNode也创建完了,进入patch过程渲染节点。
派发更新
依赖收集的目的就是当修改数据的时候,可以对相关的依赖派发更新
当修改某个属性值的时候,会执行属性的setter
方法
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
// Object.getOwnPropertyDescriptor 返回对象上一个自有属性对应的属性描述符
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 () {},
set: function reactiveSetter (newVal) {
// 获取 旧的属性值
const value = getter ? getter.call(obj) : val
// 如果新旧属性值相等,直接返回
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
// 开发环境下,如果传入了 customSetter 执行 customSetter
// 现在看来只有一种情况是 作为 props 的时候会报错
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (getter && !setter) return
// 设置新值
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 如果新值是对象,给新值添加响应
childOb = !shallow && observe(newVal)
// 通知所有的订阅者更新
dep.notify()
}
})
}
复制代码
setter
方法会先比对新老属性值是否相等,如果不相等,则设置新值,执行dep.notify()
,通知所有订阅者更新
dep.notify
是 Dep 类的一个方法:
notify () {
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
subs.sort((a, b) => a.id - b.id)
}
// 触发 subs 中每个 Watcher 的 update 方法
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
复制代码
在依赖收集过程中,依赖此属性的 Watcher 都会添加到 subs
,dep.notify
就是遍历subs
,触发每个 Watcher 的update
方法
update () {
// 渲染 Watcher 的 lazy 是 false,在计算属性的一节中会说这块
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
// 渲染 Watcher 的 sync 也是 false,在 watch 一节中会说
this.run()
} else {
// 渲染 Watcher 会执行 queueWatcher
queueWatcher(this)
}
}
复制代码
代码比较简单,对于渲染 Watcher 而言,就是执行 queueWatcher
方法
queueWatcher
方法定义在src/core/observer/scheduler.js
中
const queue: Array<Watcher> = [] // 队列,存储将要执行的 Watcher
let has: { [key: number]: ?true } = {} // 保证同一个 Watcher 只添加一次
let waiting = false // 保证对 nextTick(flushSchedulerQueue) 的调用逻辑在同一时刻只有一次
let flushing = false
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
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
nextTick(flushSchedulerQueue)
}
}
}
复制代码
queueWatcher
方法先验证传入的Watcher有没有在队列中,保证同一个 Watcher 只添加一次;然后如果flushing
为false
,则将 Watcher 添加到队列中;至于flushing
为true
的情况在 watch 一节中会说;接下来如果waiting
为false
,说明当前时刻没有nextTick
等待执行,调用nextTick(flushSchedulerQueue)
从上面逻辑中不难看出, Vue 在做派发更新的时候并不会每次数据改变都触发 watcher
的回调,而是把这些 watcher
先添加到一个队列里,然后在nextTick
后执行 flushSchedulerQueue
接下来看 flushSchedulerQueue
的实现,它的定义在 src/core/observer/scheduler.js
中
export const MAX_UPDATE_COUNT = 100
let circular: { [key: number]: number } = {}
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
// 对队列中的Watcher排序,根据 id 从小到大
queue.sort((a, b) => a.id - b.id)
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
// 渲染Watcher 有 before 方法,会执行 beforeUpdate 钩子, 先父后子
watcher.before()
}
id = watcher.id
// 将 has 里面对应的 id 变成 null
has[id] = null
// 调用 watcher.run 方法触发更新
watcher.run()
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
// 每个watcher执行一次,circular[id]就加一
circular[id] = (circular[id] || 0) + 1
// 当执行次数大于 100 时,会报错,循环执行
if (circular[id] > MAX_UPDATE_COUNT) {
warn()
break
}
}
}
// ...
const updatedQueue = queue.slice()
// 清空 queue、has、circular,并将 waiting、flushing 置为 false
resetSchedulerState()
// ...
callUpdatedHooks(updatedQueue)
}
复制代码
当执行到下一队列时,会执行flushSchedulerQueue
函数,首先对对队列中的Watcher排序,根据 id 从小到大:
-
- 组件更新是从父到子。(因为父组件总是在子组件之前创建)
-
- 组件的
User Watcher
在Render Watcher
之前执行(因为先初始化的watch
,在初始化过程中会创建User Watcher
)
- 组件的
-
- 如果一个组件在父组件的 Watcher 执行期间被销毁,那么它对应的 Watcher 执行都可以被跳过,所以父组件的 Watcher 应该先执行
然后遍历队列,执行watch.before
方法,Render Watcher
的before
方法就是执行组件的beforeUpdate
生命周期函数。将has
里面对应的id
变成null
,这样做的目的是在所有Watcher更新期间如果当前id
对应的Watcher又要更新,可以再次将这个Watcher
添加到队列中(比如在侦听器中修改某响应式属性的值)。然后调用watcher.run()
触发更新。
run () {
if (this.active) {
// 触发 Watcher 的 get 方法
const value = this.get()
if (
value !== this.value ||
isObject(value) ||
this.deep
) { /* ... */ }
}
}
复制代码
对于渲染 Watcher 来说 watcher.run()
方法会执行 Watcher
类的 get
方法触发组件更新,因为渲染 Watcher 的get
方法,返回的是undefined
,所以剩余if
逻辑不会执行,这些逻辑跟计算属性、watch
相关。组件更新完成之后回到flushSchedulerQueue
函数中继续触发其他Watcher的run
方法,等所有Watcher 的 run
方法执行完之后,flushSchedulerQueue
函数会执行resetSchedulerState
去清空queue
、has
、circular
,并将waiting
、flushing
置为false
。然后调用callUpdatedHooks
方法。
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
// 只有渲染 Watcher 才会将自身绑定到 vm._watcher 上
if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'updated')
}
}
}
复制代码
callUpdatedHooks
说白了就是执行组件的updated
生命周期,注意是先子后父的执行顺序。前提是vm._watcher === watcher
并且组件已经执行过mounted
生命周期,并且组件没有卸载。到此派发更新就结束了。
总结
Vue 的响应式原理就是通过Object.defineProperty
去拦截数据的访问和修改;当执行组件render
函数时,如果使用了响应式属性,会触发该属性的getter
,并将组件的Render Watcher
添加到此属性的dep.subs
中这个过程叫做依赖收集。
当修改响应式属性时,触发setter
,会通知dep.subs
中所有Watcher
更新,从而重新渲染组件。
官网上的响应式原理图
数组响应式
对于数组,Vue重写了 7 个方法;当获取数组数据时,在getter
中会调用childOb.dep.depend()
,将当前 Watcher 添加到数组Observer
实例的this.dep
中。当调用重写的方法时,会手动通知数组Observer
实例的dep.subs
中的所有 Watcher 更新
Vue.prototype.$set
向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性
当通过this.$set
修改对象或数组数据时,也会触发视图更新,比如
this.$set(this.arr, 1, '修改数组元素')
this.$set(this.obj, 'test', '修改对象属性')
复制代码
先看下 set
的代码
export function set (target: Array<any> | Object, key: any, val: any): any {
// ...
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
// 如果是数组则调用 splice 方法触发更新
target.splice(key, 1, val)
return val
}
// 如果 target 中已经存在 key,说明是响应式属性,不需要后续操作
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
// ...
// target 不是 Observer 实例
if (!ob) {
target[key] = val
return val
}
// 将 新增的 key 添加到 ob.value 中并添加响应
defineReactive(ob.value, key, val)
// 手动触发依赖通知
ob.dep.notify()
return val
}
复制代码
Vue.prototype.$set
方法也是通过手动触发通知的方式让视图更新,并对新增属性添加响应。
如果target
是数组,则调用数组的splice
方法,因为 Vue 重写了数组的splice
方法,所有最后在重写的splice
方法内手动触发更新;如果新增元素是一个对象,会对这个对象添加响应;
如果target
是一个响应式对象,会获取__ob__
属性(值为Observer
实例),通过defineReactive
,将新增属性添加到target
上并添加响应;然后手动触发更新。
组件中的data为什么是一个函数
一个组件被复用多次,会创建多个实例。每个实例在初始化过程中都会从options
中获取data
,如果 data
是一个对象的话,每个组件实例中data
的指向是相同的,会造成冲突;所以如果是一个返回对象的函数的话,就可以解决这个问题。