本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Vue 响应式原理
Vue的响应式的核心是利用了Object.defineProperty API(不清楚响应式对象逻辑可以点击这里),这样我们在获取定义的响应式数据的时候,就会触发数据的get方法(了解get实现原理可以点击这里),改变响应式数据的时候,就会触发数据的set方法。那这两个方法内部又实现了哪些逻辑呢? 本节我们去分析Vue源码,当改变响应式数据的值的时候,了解Vue的内部做了哪些事情:
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 创建Dep实例
const dep = new Dep()
// 获取obj描述符
const property = Object.getOwnPropertyDescriptor(obj, key)
// 不可配置则直接返回,不做响应式处理
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
// 存储预先定义的get方法
const setter = property && property.set
......
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
......
set: function reactiveSetter (newVal) {
// 计算value的值
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
// 新赋的值与之前的值作对比,如果值相等,直接返回
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
......
// setter有定义,调用setter
if (setter) {
setter.call(obj, newVal)
} else {
// 未定义setter,直接将新值赋给旧值
val = newVal
}
// 这儿的逻辑之后的文章去分析
childOb = !shallow && observe(newVal)
// 通知dep去更新
dep.notify()
}
})
}
可以看到,当改变响应式数据的值的时候,set方法会将新值与旧值坐下对比,相等的情况下直接返回,不做任何处理。不相等的情况下再对旧值赋值,同时调用dep的notify方法。在依赖收集的章节我们也分析了Dep类中的一些方法,不清楚可以点击这里,接下来我们去分析dep.notify函数,定义在dep类中:
/**
* 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 = []
}
...
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
// 遍历订阅这个dep的所有Watcher,依次执行Watcher.update函数
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
继续去看Watcher上的update函数:
Watcher.update()
// 定义在Watcher类中
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
/* istanbul ignore else */
// 判断是否是计算属性Watcher
if (this.computed) {
// A computed property watcher has two modes: lazy and activated.
// It initializes as lazy by default, and only becomes activated when
// it is depended on by at least one subscriber, which is typically
// another computed property or a component's render function.
if (this.dep.subs.length === 0) {
// In lazy mode, we don't want to perform computations until necessary,
// so we simply mark the watcher as dirty. The actual computation is
// performed just-in-time in this.evaluate() when the computed property
// is accessed.
this.dirty = true
} else {
// In activated mode, we want to proactively perform the computation
// but only notify our subscribers when the value has indeed changed.
this.getAndInvoke(() => {
this.dep.notify()
})
}
} else if (this.sync) {
// 自定义Watcher中定义了sync(同步)会进入这个逻辑
this.run()
} else {
// render Watcher进入这个逻辑,我们本次还是分析render Watcher
queueWatcher(this)
}
}
继续看queueWatcher函数:
queueWatcher
const queue: Array<Watcher> = []
let has: { [key: number]: ?true } = {}
let flushing = false
let waiting = false
/**
* Push a watcher into the watcher queue.
* Jobs with duplicate IDs will be skipped unless it's
* pushed when the queue is being flushed.
* 将一个观察者推入观察者队列。具有重复id的作业将被跳过,除非它在队列刷新时被推送。
*/
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
// 如果id没有缓存过,进入逻辑
if (has[id] == null) {
has[id] = true
if (!flushing) {
// flush为false的时候,向queue队列push Watcher
queue.push(watcher)
} else {
// 这段逻辑是在flushing过程中如果queue中出现watcher变更的情况会进入
// if already flushing, splice the watcher based on its id
// 如果已经刷新,则根据观察者的id拼接观察者
// if already past its id, it will be run next immediately.
// 如果已经超过了它的id,它将立即运行。
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) {
// wating置为true,保证nextTick执行一次
waiting = true
// 执行nextTick
nextTick(flushSchedulerQueue)
}
}
}
从上面的代码可以看出,queueWatcher是将Watcher推入queue数组中进行存储,并执行nextTick,关于nextTick,可以查看我的另一篇分析文章,这个函数就是将放在里面的函数放在下一个tick中去执行,等同步任务全部执行完再触发。
从这里我们也清楚了一个问题,当改变数据触发页面重新渲染,这是个异步的过程。
/**
* Flush both queues and run the watchers.
*/
function flushSchedulerQueue () {
// flushing赋值为true
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.
// 1. 组件的更新是先父后子
// 2. 组件的自定义watcher应该在render watcher前面运行(因为自定义watcher创建在先)
// 3. 如果组件在父组件watcher运行期间被销毁,那么他应该被跳过
queue.sort((a, b) => a.id - b.id)
// do not cache length because more watchers might be pushed
// as we run existing watchers
// queue的长度不要缓存,因为我们在运行现有的watchers的时候,可能会推入新的watchers
for (index = 0; index < queue.length; index++) {
// 依次拿到watcher
watcher = queue[index]
// 如果watcher有before属性,则执行
if (watcher.before) {
// render Watcher的时候,这儿是调用生命周期函数beforeUpdated
watcher.before()
}
id = watcher.id
// 将has[id]重置
has[id] = null
// 执行Watcher.run,下面我们看这个逻辑
watcher.run()
// in dev build, check and stop circular updates.
// 这个地方防止无限循环bug,有一种情况会进入这里
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()
// 重置队列状态,flushing,waiting等置为初始状态
resetSchedulerState()
// call component updated and activated hooks
callActivatedHooks(activatedQueue)
// 调用updated生命周期函数
callUpdatedHooks(updatedQueue)
// devtool hook
/* istanbul ignore if */
if (devtools && config.devtools) {
devtools.emit('flush')
}
}
watcher.run()与getAndInvoke():
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run () {
// this.active为true
if (this.active) {
// 执行getAndInvoke
this.getAndInvoke(this.cb)
}
}
getAndInvoke (cb: Function) {
// 调用Watcher.get函数计算value值
const value = this.get()
// 判断新值与旧值是否相同,执行传入的cb函数,render Watcher 这儿传入的是个空函数
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
this.dirty = false
if (this.user) {
// 自定义watcher进入这个逻辑
try {
cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
// computed Watcher 进入这个逻辑
cb.call(this.vm, value, oldValue)
}
}
}
到这里我们清楚了,当我们改变数据值的时候,会遍历数据中实例化的dep收集的依赖,通知Watcher做更新,将所有的Watcher推入一个队列,放在nextTick的时候去执行,所以页面的更新渲染是个异步的过程。
执行的过程中,会对队列中的watchers进行排序,排序完成后遍历queue队列,执行钩子函数beforeUpdated,再依次执行定义的Watcher.get函数,完成queue的遍历后,再执行钩子函数updated。