非侵入性的响应式系统是vue最独特的特性之一。作为一个基于MVVM的前端框架,vue的数据模型只是一个普通的 js 对象。当我们修改数据对象时,视图会进行更新。习惯了响应式系统带给我们的便利之外,我们有必要理解其工作原理。接下来,我们来研究一下vue响应式系统的底层细节。
vue内部使用Object.defineProperty
把我们通过data定义的数据对象上的所有属性转换成getter
和setter
方法。当访问这些属性的时候,watcher
实例将属性收集为依赖。当修改这些属性的时候,这些依赖又会通知watcher
实例对页面进行更新。
vue内部实际上使用了两次Object.defineProperty
来转换数据对象的属性。
第一次是对vue实例使用Object.defineProperty
。数据对象在vue内部有一个副本_data
。在vue底层对数据对象的访问和修改都是对 _data
的操作。通过 Object.defineProperty
将 _data
所有的属性全部代理到了vue实例上。当我们操作vue实例的属性时,就实现了对_data
的操作。此外,vue底层会检查data、props、methods定义的属性名和方法名,如果命名有冲突在开发环境中会有提示。
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
/**
* 使用在target上创建key代理this[sourceKey]对象上对应的key
* @param {Object} target 代理对象
* @param {string} sourceKey 被代理对象的key
* @param {string} key 代理对象的key
*/
export function proxy (target: Object, sourceKey: string, key: string) {
// 创建getter
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
// 创建setter
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
// 初始化data状态
function initData (vm: Component) {
let data = vm.$options.data
// 获取data的值
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 使用vue实例代理data对象的所有属性
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
)
}
}
// data中的属性名不能与props中重复
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)) {
// 如果data中的属性名不是保留字段,则在vue实例上创建对应的属性代理_data中的属性。
proxy(vm, `_data`, key)
}
}
// 为data对象创建观察者,观察data对象中的变化,以自动更新页面
observe(data, true /* asRootData */)
}
第二次是对数据对象使用Object.defineProperty
。vue底层对数据对象的每个属性调用defineReactive
,将数据对象的属性全部转换成了getter
和setter
属性。在getter
中,vue会为每个属性创建一个dep
(依赖),如果有watcher
实例访问了它,则被收集为依赖,在setter
中,dep
会通知所有订阅了该依赖的watcher
实例去更新页面。
/**
* 将数据对象的属性转成getter和setter属性
* @param {Object} obj 目标对象
* @param {string} key 属性
* @param {*} val 属性值
* @param {Function} customSetter 自定义setter函数
* @param {boolean} shallow 是否浅复制
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 创建依赖对象
const dep = new Dep()
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
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
// 数据变化后,通知watcher更新页面
dep.notify()
}
})
}
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
这一模式中的两个关键角色是观察者和被观察者,一个被观察者可以有任意数目的之相依赖的观察者,一旦被观察者的状态发生改变,所有的观察者都将得到通知。得到状态改变的通知后,每个观察者都会及时更新自己的状态,以与目标状态同步,这种交互方式也称为发布-订阅模式。被观察者是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它和接收通知。
在社区里,开发者一直对观察者模式和发布订阅模式的定义一直有争论。两者都是实现了一种对象间一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都将得到通知,并自动更新。两者只是在结构上略有不同,分辨模式的关键是意图而不在结构。在实践中对此不必过分较真。
vue响应式系统的实现也是应用了观察者模式。在vue中,dep
对象扮演着被观察者,watcher
对象扮演着观察者。。当数据对象的属性被访问时,会触发getter
方法。在getter
方法中,与当前属性所对应的dep
会调用depend
方法把当前watcher
对象添加到订阅者队列中。
当数据对象的属性发生改变时,会触发setter
方法。在setter
方法中,与当前属性所对应的dep
对象调用notify
方法通知订阅列表中的所有watcher
进行更新。下文贴出dep
和watcher
对象的主要代码实现,大家在脑子里有个大致印象。
被观察者Dep
export default class Dep {
// 当前watcher
static target: ?Watcher;
// 依赖的id
id: number;
// 订阅该依赖的watcher队列
subs: Array;
constructor () {
// 每个dep实例分配唯一的id
this.id = uid++
// subs保存所有的订阅者
this.subs = []
}
/**
* 添加订阅者,一个watcher是一个订阅者
* @param {Watcher} sub
*/
addSub (sub: Watcher) {
this.subs.push(sub)
}
/**
* 移除订阅者,一个watcher是一个订阅者
* @param {Watcher} sub
*/
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
/**
* 将dep实例添加为当前watcher的依赖
*/
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()
}
}
}
观察者Watcher
let uid = 0
export default class Watcher {
// 被观察的Vue 实例
vm: Component;
// 观察 Vue 实例上的一个表达式或者一个函数计算结果的变化
expression: string;
// 被观察的属性值发生变化后触发的回调函数,回调函数得到的参数为新值和旧值。一般用在watch属性上
cb: Function;
id: number;
// 为了发现对象内部值的变化,可以在选项参数中指定 deep: true。注意监听数组的变更不需要这么做。
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array;
newDeps: Array;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
// 保留对vue实例的引用
this.vm = vm
// 如果是render watcher
if (isRenderWatcher) {
vm._watcher = this
}
// 将当前watcher实例添加到vue实例的_watchers数组中
vm._watchers.push(this)
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
// watcher实例收集的依赖实例数组
this.deps = []
// watcher实例重新收集的依赖实例数组
this.newDeps = []
// watcher实例收集的依赖实例的id组成的set
this.depIds = new Set()
// watcher实例重新收集的依赖实例的id组成的set
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// 将expression转换为一个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()
}
/**
* 计算getter,重新收集依赖
*/
get () {
// 将watcher添加到全局的targetStack中,并作为当前watcher
pushTarget(this)
let value
const vm = this.vm
try {
// 根据getter计算value的值
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
}
/**
* 1. 如果dep已经在watcher中,则不作任何处理
* 2. 如果是新增的依赖,那么将dep添加到watcher的依赖数组里
* 3. 将watcher加到dep的订阅者数组里
*/
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
// 将watcher从dep的订阅者队列中删除
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
// 更新depIds
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
// 更新deps
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) {
this.run()
} else {
queueWatcher(this)
}
}
/**
* 任务调度接口,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)
}
}
}
}
/**
* 计算watcher的值,只有lazy watcher(computed属性对应的watcher)会调用
*/
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* 一次性订阅此watcher收集的依赖。
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* 将watcher从所有依赖的订阅者列表中删除
*/
teardown () {
if (this.active) {
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
watcher
根据使用场景的不同可以分成三类:
render watcher
每个vue实例在创建的时候,都会创建一个render watcher
。render watcher
会将页面渲染时所访问的数据对象属性作为依赖收集。当数据对象属性发生改变时,通知render watcher
重新渲染页面。
user watcher
有时我们可能希望在数据变化时执行异步或开销较大的操作。我们可以在watch
选项中,创建一个自定义的监听器,来响应数据的变化。vue底层会给在watch
中创建的每个监听器,创建一个watcher
实例。watcher
实例同样会将自己所监听的数据属性作为依赖收集。当数据对象属性发生改变时,watcher
会调用在watch
中定义的回调函数。回调函数默认在下个事件循环执行。如果用户指定了immediate
属性,则会立即调用。除了在watch
选项中自定义监听器外,还可以通过$watch
定义watcher
实例。watch
选项在内部也是调用的$watch
创建的watcher
实例。因为这里的watcher
是用户自己创建的,因此也称之为user watcher
。
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
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]
}
return vm.$watch(expOrFn, handler, options)
}
// 为Vue实例定义$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)
// 如果immediate为true,立即调用回调函数
if (options.immediate) {
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
return function unwatchFn () {
watcher.teardown()
}
}
computed watcher
有时我们在模板中使用的数据需要经过复杂的计算后再使用。这种情况,我们可以在computed
选项中定义一个计算属性,对原始数据的计算加工都放在计算属性的处理函数中。针对计算属性,vue内部会为每个计算属性创建一个watcher
实例,我们称之为computed watcher
。在计算属性的处理函数中所访问的数据,会被computed watcher
收集会依赖。计算属性的结果会被缓存,当数据发生变化时,才会通知computed watcher
重新计算。
function initComputed (vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
// 对所有的计算属性
for (const key in computed) {
const userDef = computed[key]
// 将计算属性对应的函数作为计算属性的getter方法
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (process.env.NODE_ENV !== 'production' && getter == null) {
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
if (!isSSR) {
// 为每个计算属性创建一个watcher实例,watcher实例可以收集响应式依赖,并在响应式依赖发生变化时,更新计算属性的值
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
if (!(key in vm)) {
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
// 计算属性不能与data及prop中的属性重名
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm)
}
}
}
}
/**
* 将计算属性以响应式依赖的形式混入vue实例,使之与data中定义的属性一样具备响应式的特性
* @param {*} target
* @param {*} key
* @param {*} userDef
*/
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
const shouldCache = !isServerRendering()
// 计算属性既可以是一个函数也可以是一个包含getter和setter方法的对象,因此需要分别处理
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
if (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
/**
* 创建计算属性的getter方法
* @param {*} key
*/
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 重新计算计算属性的值
if (watcher.dirty) {
watcher.evaluate()
}
// 将计算属性添加为watcher的依赖
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
function createGetterInvoker(fn) {
return function computedGetter () {
return fn.call(this, this)
}
}
vue 在更新 DOM 时是异步执行的。当侦听到数据变化,数据所对应的dep
对象将通知所有的订阅者watcher
进行更新。 watcher
得到需要更新的通知后,并不是马上就执行。而是开启一个队列,并将同一事件循环中所有需要执行更新任务的watcher
推入队列。如果同一个watcher
在同一事件循环中被通知了多次,只会被推入到队列中一次。通过异步缓冲的方式,去除重复更新任务可以有效地避免不必要的计算和 DOM 操作。然后,在下一个的事件循环中,vue 会调用队列中的watcher
执行更新(已去重) 工作。
例如,当我们设置 vm.someData = 'new value'
,该组件不会立即重新渲染。此时如果有个DOM元素的textContent
使用了someData
,那么在vm.someData = 'new value'
代码后面直接获取该节点的textContent
,你并不能得到new value
的结果,因为DOM更新是异步的。如果希望得到的是更新之后的结果,可以使用nextTick(callback)
,在callback
中获取。
调度器
vue 内部设计了一个异步任务调度器来管理异步任务。vue会调用queueWatcher
将同一个事件循环内待更新的watcher
实例添加到queue
中。然后通过nextTick
开启一个异步任务,在下一个事件循环中,调用flushSchedulerQueue
执行queque
中的更新任务。更新完成,重置调度器的状态,并且调用组件的updated
和activated
钩子(keep-alive
包裹的组件)。
export const MAX_UPDATE_COUNT = 100
// 存储watcher的队列
const queue: Array = []
// 激活状态下的子组件
const activatedChildren: Array = []
// 存储watcher的id,用以标识队列中是否已经存在该watcher
let has: { [key: number]: ?true } = {}
// 循环引用对象
let circular: { [key: number]: number } = {}
let waiting = false
// 是否在处理异步队列
let flushing = false
let index = 0
/**
* 重置调度器的状态
*/
function resetSchedulerState () {
index = queue.length = activatedChildren.length = 0
has = {}
if (process.env.NODE_ENV !== 'production') {
circular = {}
}
waiting = flushing = false
}
export let currentFlushTimestamp = 0
let getNow: () => number = Date.now
if (inBrowser && !isIE) {
const performance = window.performance
if (
performance &&
typeof performance.now === 'function' &&
getNow() > document.createEvent('Event').timeStamp
) {
getNow = () => performance.now()
}
}
/**
*清空所有的队列并执行watcher的更新逻辑
*/
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
// 队列按照watcher的id升序排序,目的是确保:
// 1. 组件总是从父向子进行更新
// 2. 用户创建的watcher先于渲染watcher更新
// 3. 如果组件在父组件的watcher运行时被销毁,该组件的watcher可以跳过处理
queue.sort((a, b) => a.id - b.id)
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
// 调用vue实例的beforeUpdate钩子
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
// vue实例更新
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
}
}
}
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
// 调用 updated 和 activated 钩子
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
if (devtools && config.devtools) {
devtools.emit('flush')
}
}
// 调用vue实例的updated钩子
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'updated')
}
}
}
/**
* 添加到激活组件队列
* @param {*} vm
*/
export function queueActivatedComponent (vm: Component) {
vm._inactive = false
activatedChildren.push(vm)
}
// 调用vue组件实例的activated钩子
function callActivatedHooks (queue) {
for (let i = 0; i < queue.length; i++) {
queue[i]._inactive = true
activateChildComponent(queue[i], true /* true */)
}
}
/**
* 将watcher添加到异步队列
* @param {*} watcher
*/
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
// 如果watcher不在队列中,将它添加到队列中
if (has[id] == null) {
has[id] = true
// 如果异步队列未开始执行,添加watcher
if (!flushing) {
queue.push(watcher)
} else {
// 如果已经在处理中了,如果还没执行到该异步任务,插入到队列
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
}
nextTick
vue 在内部的异步队列是通过nextTick
方法实现的。该方法也是vue实例方法$nextTick
的底层实现。 $nextTick(cb)
会把回调函数cb
添加到队列callbacks
中,然后调用timerFunc
方法开启异步任务。在下一个事件循环中调用flushCallbacks
执行callbacks
中的任务。
由于Promise.then
、MutationObserver
会创建微任务,而setImmediate
和setTimeout
会创建宏任务。相比于宏任务,微任务能更快地得到执行,因此timerFunc
方法优先使用原生的 Promise.then
、MutationObserver
来来执行异步任务,其次采用 setImmediate
。如果以上特性执行环境均不支持,才降级为 setTimeout(fn, 0)
。
// 是否使用了微任务
export let isUsingMicroTask = false
// 异步任务队列
const callbacks = []
// 控制上一次事件循环中的回调函数是否已经执行完毕。true:表示正在处理中;false:表示已经处理完成
let pending = false
/**
* 执行并清空异步任务队列
*/
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let timerFunc
/**
* nextTick方法的实现巧妙地借用了微任务队列,nextTick底层实现中主要依赖MutationObserver和promise来访问微任务队列
* MutationObserver在ios>=9.3.3中有严重bug,所以更多情况下,nextTick是借助promise来构造微任务
*/
// 1. 优先考虑使用promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// 2. 其次选择使用MutationObserver。(PhantomJS、iOS7、Android4.4中不支持promise)
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// 3. 第三使用 setImmediate ,浏览器环境下不支持。setImmediate虽然同样也是使用宏队列,但是相比setTimeout仍是一个更好的选择。
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// 如果promise、mutationObserver、setImmediate均不支持,使用setTimeout。
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
/**
* 下一个事件循环中执行回调函数
* @param {*} cb
* @param {*} ctx
*/
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// 1. 首先在callbacks数组中添加一项回调函数
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
// 2. 如果当前没有需要处理的任务,调用 timerFunc 异步执行所有的回调函数
if (!pending) {
pending = true
// 通过异步方法,将回调函数的调用放到微任务队列
timerFunc()
}
// 如果未指定回调函数,则返回一个promise对象
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
vue通过Object.definePropert
将数据对象中的所有属性转成getter
和setter
。基于观察者模式,使用watcher
对象作为观察者在getter
中订阅数据对应的dep
对象。在setter
中,当数据发生改变时,使用dep
对象作为被观察者,通知watcher
进行更新。以上就是vue响应式系统的底层实现原理。
推荐阅读
深入理解Vue实例生命周期
keep-alive是如何保持组件状态的
vue组件实现原理解析
vue渲染过程解析-VDOM &DOM
vue编译过程分析
Vue执行流程解析
好文我在看????