# 渲染函数的观察者
理解数据响应系统中另一个很重要的部分,即 Watcher ,我们知道正是由于 Watcher 对所观察字段的求值才触发了字段的 get,从而才有了收集到该观察者的机会
//通过 mountComponent 函数去真正的挂载组件,在src/core/instance/lifecycle.js
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {//渲染函数是否存在
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') {
/* istanbul ignore if */
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el || el) {
warn('something',
vm
)
} else {
warn(
'Failed to mount component: template or render function not defined.',
vm
)
}
}
}
callHook(vm, 'beforeMount')
//定义并初始化 updateComponent 函数,这个函数将用作创建 Watcher 实例时传递给 Watcher 构造函数的第二个参数
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
//统计了 vm._render() 函数以及 vm._update() 函数的运行性能
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
//updateComponent 把渲染函数生成的虚拟DOM渲染成真正的DOM,其实在 vm._update 内部是通过虚拟DOM的补丁算法(patch)来完成的
//vm._render 函数的作用是调用 vm.$options.render 函数并返回生成的虚拟节点(vnode)
// vm._update 函数的作用是把 vm._render 函数生成的虚拟节点渲染成真正的 DOM
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
hydrating = false
//上面的代码中 Watcher 观察者实例将对 updateComponent 函数求值,我们知道 updateComponent 函数的执行会间接触发渲染函数(vm.$options.render)的执行,而渲染函数的执行
//则会触发数据属性的 get 拦截器函数,从而将依赖(观察者)收集,当数据变化时将重新执行 updateComponent 函数,这就完成了重新渲染
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
# 初识 Watcher
export default class Watcher {
//expOrFn观察的表达式,被观察的表达式的值变化时的回调函数 cb,一些传递给当前观察者对象的选项 options
// 以及一个布尔值 isRenderWatcher 用来标识该观察者实例是否是渲染函数的观察者
//Watcher 的原理是通过对“被观测目标”的求值,触发数据属性的 get 拦截器函数从而收集依赖
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
//是否是渲染函数的观察者,只有在 mountComponent 函数中创建渲染函数观察者时这个参数为真
if (isRenderWatcher) {
//组件实例的 _watcher 属性的值引用着该组件的渲染函数观察者
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 //观察者当数据变化时是否同步求值并执行回调
} 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)
const bailRE = /[^\w.$]/ //不能是 字母 或 数字 或 下划线 . $
function parsePath (path: string): any {
if (bailRE.test(path)) {
return
}
const segments = path.split('.')
//遍历 segments 数组循环访问 path 指定的属性值。这样就触发了数据属性的 get 拦截器函数。
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
//Watcher 只接受简单的点(.)分隔路径
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
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
//求值的目的有两个,第一个是能够触发访问器属性的 get 拦截器函数,是依赖被收集的关键,第二个是能够获得被观察目标的值
get () {
class Dep {
// 省略...
}
Dep.target = null
const targetStack = []
function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
function popTarget () {
Dep.target = targetStack.pop()
}
//Dep.target 属性赋值的,pushTarget 函数会将接收到的参数赋值给 Dep.target 属性
//传递给 pushTarget 函数的参数就是调用该函数的观察者对象,所以 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 {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
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)) {
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)
}
}
//典型的引用类型变量交换值的过程,最终的结果就是 newDepIds 属性和 newDeps 属性被清空,
//并且在被清空之前把值分别赋给了 depIds 属性和 deps 属性,这两个属性将会用在下一次求值时避免依赖的重复收集
//newDepIds 和 newDeps 这两个属性的值所存储的总是当次求值所收集到的 Dep 实例对象,
//而 depIds 和 deps 这两个属性的值所存储的总是上一次求值过程中所收集到的 Dep 实例对象。
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
}
update () {
if (this.lazy) {
this.dirty = true
} else if (this.sync) {//创建观察者实例对象时传递的第三个选项参数中的 sync 属性的值,代表了当变化发生时是否同步更新变化
this.run()
} else {
queueWatcher(this) //将当前观察者对象放到一个异步更新队列,这个队列会在调用栈被清空之后按照一定的顺序执行
}
}
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) //回调函数的作用域修改为当前 Vue 组件对象,然后传递了两个参数,分别是新值和旧值。
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
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()
}
}
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)
}
//当一个属性与一个观察者建立联系之后,属性的 Dep 实例对象会收集到该观察者对象,同时观察者对象也会将该 Dep 实例对象收集,
//这是一个双向的过程,并且一个观察者可以同时观察多个属性
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this) //将当前观察者实例对象从所有的 Dep 实例对象中移除
}
this.active = false
}
}
}
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)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
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方法调用
let has: { [key: number]: ?true } = {} //空对象
const queue: Array<Watcher> = []
let flushing = false //更新开始时会将 flushing 变量的值设置为 true,代表着此时正在执行更新
let waiting = false
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
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue() //flushSchedulerQueue 函数的作用之一就是用来将队列中的观察者统一执行更新的
return
}
nextTick(flushSchedulerQueue) //把nextTick 看做 setTimeout(fn, 0)
}
}
}
# nextTick
任务队列并非只有一个队列,总的来说我们可以将其分为 microtask(微任务) 和 macrotask(宏任务),当调用栈空闲后每次事件循环只会从 (macro)task 中读取一个任务并执行,而在同一次事件循环内会将 microtask 队列中所有的任务全部执行完毕,且要先于 (macro)task。另外 (macro)task 中两个不同的任务之间可能穿插着UI的重渲染,那么我们只需要在 microtask 中把所有在UI重渲染之前需要更新的数据全部更新,这样只需要一次重渲染就能得到最新的DOM,要优先选用 microtask 去更新数据状态而不是 (macro)task,最优的选择是使用 Promise
以下事件属于宏任务:
- setTimeout
- MessageChannel
- postMessage
- setImmediate
以下事件属于微任务:
- Promise()
- new MutaionObserver()
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0) //常量保存一份 callbacks 的复制
callbacks.length = 0//清空
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
//将一个回调函数注册为 (macro)task 的方式有很多,如 setTimeout、setInterval 以及 setImmediate
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {//setImmediate 拥有比 setTimeout 更好的性能,ie支持
macroTimerFunc = () => {
setImmediate(flushCallbacks)
}
} else if (typeof MessageChannel !== 'undefined' && (//messageChannel
isNative(MessageChannel) ||
// PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = () => {
port.postMessage(1)
}
} else {
/* istanbul ignore next */
macroTimerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
if (typeof Promise !== 'undefined' && isNative(Promise)) {//支持promise?
const p = Promise.resolve()
microTimerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
} else {
// fallback to macro
microTimerFunc = macroTimerFunc
}
function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)//函数 cb 的作用域设置为 ctx
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {//直接调用 _resolve 函数,我们知道这个函数就是返回的 Promise 实例对象的 resolve 函数
_resolve(ctx)
}
})
if (!pending) {//回调队列是否处于等待刷新的状态
pending = true
if (useMacroTask) {
macroTimerFunc()
} else {
microTimerFunc()
}
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
# $watch和watch选项的实现
无论是 $watch
方法还是 watch
选项,他们的实现都是基于 Watcher
的封装
$watch在 src/core/instance/state.js
文件的 stateMixin
函数中
//$watch 方法允许我们观察数据对象的某个属性,当属性变化时执行回调,
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) { //用来在属性或函数被侦听后立即执行回调
cb.call(vm, watcher.value)
}
return function unwatchFn () {
watcher.teardown()
}
}
//将纯对象形式的参数规范化一下,然后再通过 $watch 方法创建观察者
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]//methods中同名函数作为回调
}
return vm.$watch(expOrFn, handler, options)//通过 $watch 方法创建观察者
}
那么watch 选项是如何初始化的,找到 initState
函数
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)
}
}
function initWatch (vm: Component, watch: Object) {
//对 watch 选项遍历,然后通过 createWatcher 函数创建观察者对象
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {//watch[key]可以是数组
//使用 watch 选项时可以通过传递数组来实现创建多个观察者
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}