vue源码解析之实例方法
上一篇分析了vue的整个生命周期在initMixin
函数中;下面继续分析下数据,事件和生命周期相关的方法;
// 源码位置 src/core/instance/index.js
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
// 整个vue的生命周期
initMixin(Vue)
// 数据相关的方法
stateMixin(Vue)
// 事件相关的方法
eventsMixin(Vue)
// 生命周期相关的方法
lifecycleMixin(Vue)
export default Vue
数据相关的方法
数据相关的方法有三个,分别是set、set、set、delete、$watch;
$watch
// 源码位置 src/core/instance/state.js
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
// 是否是一个对象
if (isPlainObject(cb)) {
// 是对象通过createWatcher创建watcher
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
// 标注为用户添加
options.user = true
// 不是对象,就创建watcher
const watcher = new Watcher(vm, expOrFn, cb, options)
// 如果是立即执行
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
// 收集依赖
pushTarget()
// 执行cb
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
// 返回一个取消监听的函数
return function unwatchFn () {
// 取消监听
watcher.teardown()
}
}
如果回调函数是对象就通过createWatcher进行创建;
/*
处理这种形式
this.$watch('a.b', {
handler: () {},
deep: false,
})
*/
// 是否是一个对象
if (isPlainObject(cb)) {
// 是对象通过createWatcher创建watcher
return createWatcher(vm, expOrFn, cb, options)
}
createWatcher内部,获取到对象的handler属性,再次调用$watch进行监听
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
// 是对象
if (isPlainObject(handler)) {
// 把值作为options配置项
options = handler
// 获取到值中的handler属性
handler = handler.handler
}
// 如果是字符串
if (typeof handler === 'string') {
// 直接获取到实例上的这个属性
handler = vm[handler]
}
// 进行监听
return vm.$watch(expOrFn, handler, options)
}
$watch是提供给用户调用的,因此需要标注下,方便区分是内部调用还是外部调用;接着创建Watcher实例进行监听
options = options || {}
// 标注为用户添加
options.user = true
// 不是对象,就创建watcher
const watcher = new Watcher(vm, expOrFn, cb, options)
如果用户传递了立即执行,通过invokeWithErrorHandling函数执行cb函数;
// 如果是立即执行
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
// 收集依赖
pushTarget()
// 执行cb
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
最后返回一个取消监听的函数,函数内部调用watch的取消函数
// 返回一个取消监听的函数
return function unwatchFn () {
// 取消监听
watcher.teardown()
}
发现这个$watch中并没有处理deep属性,是因为deep是在Watcher类内部通过traverse处理的;
// 源码位置 src/core/observer/watcher.js
export default class Watcher {
...
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
// 处理deep属性
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
}
深度监听的原理其实就是遍历这个对象或数组,读取对象或数组上的每个属性,这样就会触发它的getter,这样就会进行依赖收集和监听,从而达到深度监听;
export function traverse (val: any) {
_traverse(val, seenObjects)
seenObjects.clear()
}
function _traverse (val: any, seen: SimpleSet) {
let i, keys
// 是否是数组
const isA = Array.isArray(val)
if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
return
}
// 防止重复操作
if (val.__ob__) {
const depId = val.__ob__.dep.id
if (seen.has(depId)) {
return
}
seen.add(depId)
}
// 是数组进行递归 是对象就遍历对象进行递归
if (isA) {
i = val.length
while (i--) _traverse(val[i], seen)
} else {
keys = Object.keys(val)
i = keys.length
while (i--) _traverse(val[keys[i]], seen)
}
}
_traverse内部就通过递归的形式处理对象或数组,以便读取它身上的每个属性;在递归的时候会获取到当前属性(_traverse(val[i], seen)
)会去触发getter;
$set
vue中给对象添加新的属性或者通过数组的下标修改数组的某个数据,修改之后视图是不会更新;因此vue提供了vm.set或this.$set方法来处理这个问题;
/*
作用:
向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。
它必须用于向响应式对象上添加新属性,因为 `Vue` 无法探测普通的新增属性
注意:
目标对象不能是vue的实例,或vue实例的根数据对象
*/
this.$set(目标对象,属性/索引,值)
// 源码位置 ./src/core/observer/index.js
export function set (target: Array<any> | Object, key: any, val: any): any {
// 如果是数组并且key是个数组中的有效数字
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 重新定义长度
target.length = Math.max(target.length, key)
// 修改这个属性
target.splice(key, 1, val)
return val
}
// 是对象并且这个属性已经在对象上存在了并且不是Object原始属性就直接赋值
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
// 获取到当前实例
const ob = (target: any).__ob__
// 如果当前对象是vue实例或是实例上的根数据对象
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
// 如果没有实例 表示不是响应式的直接添加
if (!ob) {
target[key] = val
return val
}
// 给这个对象添加属性并且设置为响应式的
defineReactive(ob.value, key, val)
// 进行监听
ob.dep.notify()
return val
}
首先判断当前对象是不是一个数组,如果是一个数组,就判断key值是否是一个有效数字(大于等于0并且为整数),如果是就比较数组的长度和key的大小,重新定义数组的长度,通过splice进行修改数据;因为splice是重写之后的方法,因此添加的属性具有响应式;
// 如果是数组并且key是个数组中的有效数字
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 重新定义长度
target.length = Math.max(target.length, key)
// 修改这个属性
target.splice(key, 1, val)
return val
}
不是数组就是对象,并且这个属性已经在这个对象上面了,并且不是Object原始属性,因此直接修改;
// 是对象并且这个属性已经在对象上存在了并且不是Object原始属性就直接赋值
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
获取到对象的_ob_属性,判断当前对象是否是vue实例或者是否是vue实例上的根数据对象,如果是就直接返回;
// 获取到当前实例
const ob = (target: any).__ob__
// 如果当前对象是vue实例或是实例上的根数据对象
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
如果_ob_不存在表示是一个普通对象,普通对象不需要添加响应式,因此直接添加属性;
// 如果没有实例 表示不是响应式的直接添加
if (!ob) {
target[key] = val
return val
}
以上都不是表示对象是一个响应式对象,通过defineReactive给这个对象添加响应式属性,再进行数据监听
// 给这个对象添加属性并且设置为响应式的
defineReactive(ob.value, key, val)
// 进行监听
ob.dep.notify()
return val
$delete
vue中无法监听到删除对象或数组上的一个属性,删除之后视图是不会更新,因此需要vm.delete或this.$delete方法进行删除;
/*
作用:
删除对象的属性。如果对象是响应式的,确保删除能触发更新视图。
这个方法主要用于避开 `Vue` 不能检测到属性被删除的限制,但是你应该很少会使用它。
注意:
目标对象不能是vue实例,或实例的根数据对象
*/
vm.$delete(目标对象, 属性或索引 )
// 源码位置 ./src/core/observer/index.js
export function del (target: Array<any> | Object, key: any) {
// 如果是数组并且是有效的索引就通过splice进行删除
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
// 获取到对象的实例
const ob = (target: any).__ob__
// 如果不是vue实例或者不是vue实例上的根数据对象
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.'
)
return
}
// 如果这个属性不是这个对象的自有属性(原型上属性)直接返回
if (!hasOwn(target, key)) {
return
}
// 直接删除这个属性
delete target[key]
// 如果不是响应式的直接返回
if (!ob) {
return
}
ob.dep.notify()
}
首先判断是不是数组,并且属性是不是有效数字,是的话就直接通过splice进行删除,因为splice方法,vue中已经进行了重写;通过splice操作的属性具有响应式;
// 如果是数组并且是有效的索引就通过splice进行删除
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
获取到当前对象的vue实例_ob_,判断当前对象是否是vue实例,或者是否是vue实例上的根数据的对象,如果是直接返回;
// 获取到对象的实例
const ob = (target: any).__ob__
// 如果不是vue实例或者不是vue实例上的根数据对象
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.'
)
return
}
判断这个属性是否是对象上的自有属性,不是直接返回
// 如果这个属性不是这个对象的自有属性(原型上属性)直接返回
if (!hasOwn(target, key)) {
return
}
剩下表示这个对象是一个Object对象,并且属性是自有属性,直接进行删除
// 直接删除这个属性
delete target[key]
如果当前对象不是vue实例表示是一个普通对象(_ob_为true表示vue实例,数据为响应式的),直接返回,否则进行依赖的更新;
// 如果不是响应式的直接返回
if (!ob) {
return
}
ob.dep.notify()
事件相关的方法
事件相关的方法有四个:$on、$once、$off、$emit;
$on
/*
作用:
监听实例上的自定义事件,事件可以由$emit触发;回调函数会接收所有传入事件触发函数的额外参数。
事件名可以是一个字符串,或者是一个数组
*/
this.$on(事件名,回调函数)
eg:
vm.$on('test', function (msg) {
console.log(msg) // '8888'
})
vm.$emit('test', '8888')
// 源码位置 src/core/instance/event.js
const hookRE = /^hook:/
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
// 如果传递的事件名是一个数组
if (Array.isArray(event)) {
// 进行遍历
for (let i = 0, l = event.length; i < l; i++) {
// 通过递归处理每一项
vm.$on(event[i], fn)
}
} else {
// 是字符串,通过key来判断是否在_events事件中心对象中,如果不存在就初始化为数组,添加到数组中
(vm._events[event] || (vm._events[event] = [])).push(fn)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
// 如果是hook开头的事件名,给当前实例添加标记
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
首先判断事件名是不是一个数组,如果是数组就遍历这个数据,通过递归的形式处理每一项;
if (Array.isArray(event)) {
// 进行遍历
for (let i = 0, l = event.length; i < l; i++) {
// 通过递归处理每一项
vm.$on(event[i], fn)
}
}
如果不是数组表示就是字符串,通过事件名判断是否在_events上存在,不存在就初始化为一个数组,再把回调添加到数组中;
// 是字符串,通过key来判断是否在_events事件中心对象中,如果不存在就初始化为数组,添加到数组中
(vm._events[event] || (vm._events[event] = [])).push(fn)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
// 如果是hook开头的事件名,给当前实例添加标记
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
_events是在生命周期初始化的时候定义的
export function initEvents (vm: Component) {
vm._events = Object.create(null)
// ...
}
$emit
/*
作用: 触发当前实例上的事件。附加参数都会传给监听器回调
*/
this.$emit(事件名,参数)
源码解析
// 源码位置 src/core/instance/event.js
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
// 从事件中心对象中获取到当前事件名的回调函数
let cbs = vm._events[event]
// 如果存在
if (cbs) {
// 如果回调函数有多个就复制一份
cbs = cbs.length > 1 ? toArray(cbs) : cbs
// 获取到参数并且转成数组
const args = toArray(arguments, 1)
const info = `event handler for "${event}"`
// 遍历回调数组
for (let i = 0, l = cbs.length; i < l; i++) {
// 执行回调函数并且传递参数
invokeWithErrorHandling(cbs[i], vm, args, vm, info)
}
}
return vm
}
首先从当前实例上的事件中心对象上获取到这个事件名的回调函数;
// 从事件中心对象中获取到当前事件名的回调函数
let cbs = vm._events[event]
获取到传递的参数,并且转成数组
// 如果回调函数有多个就复制一份
cbs = cbs.length > 1 ? toArray(cbs) : cbs
// 获取到参数并且转成数组
const args = toArray(arguments, 1)
遍历回调数组,挨个进行执行
// 遍历回调数组
for (let i = 0, l = cbs.length; i < l; i++) {
// 执行回调函数并且传递参数
invokeWithErrorHandling(cbs[i], vm, args, vm, info)
}
invokeWithErrorHandling之前分析过,就是用来执行函数的;
export function invokeWithErrorHandling (
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
) {
let res
try {
res = args ? handler.apply(context, args) : handler.call(context)
if (res && !res._isVue && isPromise(res) && !res._handled) {
res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
// issue #9511
// avoid catch triggering multiple times when nested calls
res._handled = true
}
} catch (e) {
handleError(e, vm, info)
}
return res
}
$off
/*
作用:移除自定义事件
如果没有传递参数则移除该实例上的全部自定义事件
如果传递了事件名,则移除该事件名的所有回调
如果传递了事件名和回调函数,则移除该事件名下的该回调函数
*/
this.$off(事件名,回调)
源码解析
// 源码位置 src/core/instance/event.js
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this
// all
// 如果没有传递参数,直接给事件中心重新赋值
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
// 如果事件名为数组
if (Array.isArray(event)) {
// 遍历事件名,递归进行处理
for (let i = 0, l = event.length; i < l; i++) {
vm.$off(event[i], fn)
}
return vm
}
// specific event
// 从事件中心获取到该事件的回调
const cbs = vm._events[event]
// 不存在直接返回
if (!cbs) {
return vm
}
// 如果没有传递回调函数,则直接把事件中心的当前事件全部清空
if (!fn) {
vm._events[event] = null
return vm
}
// specific handler
let cb
let i = cbs.length
// 遍历回调数组
while (i--) {
cb = cbs[i]
// 找到当前回调和传递进来的回调是否相同,相同进行删除
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
}
首先判断没有传递参数,则直接把当前实例上的事件中心对象初始为空对象(全部删除)
// 如果没有传递参数,直接给事件中心重新赋值
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
判断传递的事件名是否是一个数组,是数组就遍历进行递归处理
// 如果事件名为数组
if (Array.isArray(event)) {
// 遍历事件名,递归进行处理
for (let i = 0, l = event.length; i < l; i++) {
vm.$off(event[i], fn)
}
return vm
}
从事件中心对象上获取到对应的回调,如果回调不存在直接返回
// 从事件中心获取到该事件的回调
const cbs = vm._events[event]
// 不存在直接返回
if (!cbs) {
return vm
}
如果没传递回调函数,则直接把事件中心的对应的事件名的属性值清空
// 如果没有传递回调函数,则直接把事件中心的当前事件全部清空
if (!fn) {
vm._events[event] = null
return vm
}
剩下就表示传递了事件名和回调,则遍历事件中心获取对应事件名的回调,从中找出和传递进来的回调一样的回调进行删除
let cb
let i = cbs.length
// 遍历回调数组
while (i--) {
cb = cbs[i]
// 找到当前回调和传递进来的回调是否相同,相同进行删除
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
$once
/*
作用: 监听一个自定义事件,只触发一次,一旦触发之后就进行移除
*/
this.$once(事件名,回调函数)
源码解析
Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this
// 定义一个on函数
function on () {
// 移除on事件
vm.$off(event, on)
// 执行fn回调
fn.apply(vm, arguments)
}
// 给on上添加fn回调,目的就是用户在手动off的时候能够移除掉这个fn
on.fn = fn
// 通过$on监听event的on事件
vm.$on(event, on)
return vm
}
首先定义了一个on函数,此函数的作用就是做一层代理,当前这个事件触发之后,就会执行这个函数,函数内部首先移除了这个事件的on回调,再执行用户传递进来的fn回调,这样再此调用就没有这个事件的回调了;
// 定义一个on函数
function on () {
// 移除on事件
vm.$off(event, on)
// 执行fn回调
fn.apply(vm, arguments)
}
把用户传递进来的fn回调放在on的fn上面,目的就是用户手动调用$off
的时候能够找到这个事件,因为通过on进行代理了,不这样做在$off
中就找不到fn这个函数(使用$off
移除事件的时候,$off
内部会判断如果回调函数列表中某一项的fn
属性与fn
相同时,就可以成功移除事件了。)
// 给on上添加fn回调,目的就是用户在手动off的时候能够移除掉这个fn
on.fn = fn
通过$on添加自定义事件,回调为on函数
// 通过$on监听event的on事件
vm.$on(event, on)
生命周期相关的方法
生命周期相关的方法有四个:$mount、$forceUpdate、$nextTick、$destory;
$mount
/*
作用:
如果vue实例在实例化的时候没有接收到el选项,它就处于未挂载状态,
没有关联DOM元素,因此可以手动调用$mount进行挂载;
*/
vm.$mount( [elementOrSelector] )
$mount在生命周期中的模板编译阶段解析过;
$forceUpdate
/*
作用:
强制渲染vue实例,只会渲染当前实例和其插槽内的子组件,
不会渲染全部的子组件
*/
this.$forceUpdate()
源码解析:
// 源码位置 src/core/instance/lifecucle.js
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
源码很简单,就是调用了_watcher中的update方法进行更新;因为watcher就是数据的依赖,当数据变化的时候就会通知依赖进行更新,也就是执行watcher中的update进行更新;
$nextTick
/*
作用:
将回调延迟到下一次DOM更新循环结束执行;在修改数据之后立即使用它,
然后等待DOM更新;
注意:
没有提供回调的时候,在支持Promise的环境中会返回一个Promise
*/
this.$nextTick(回调函数,执行上下文)
vue中对数据的操作是这样描述的:
可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
源码解析
// 源码位置 /src/core/util/next-tick.js
export let isUsingMicroTask = false
const callbacks = []
let pending = false
// 执行回调函数的函数
function flushCallbacks () {
pending = false
// 复制一份回调函数
const copies = callbacks.slice(0)
// 清空callbacks
callbacks.length = 0
// 遍历回调函数并且执行它
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let timerFunc
// 兼容性判断,取决使用哪个异步方法
// promise存在通过promise执行回调
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
// 如果不是ie并且MutationObserver存在,就听过MutationObserver监听dom的变化,MutationObserver是异步的当dom变化完成之后才会执行它的回调,MutationObserver是微任务
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
// 修改dom中的内容,触发MutationObserver中的回调
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
// 如果setImmediate存在就通过setImmediate宏任务执行flushCallbacks
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {// 如果以上都不存在,就通过setTimeout执行flushCallbacks
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
首先定义了两个变量,分别是callbacks存储传递进来的回调函数和pending控制回调执行的开关;
const callbacks = []
let pending = false
定义了一个flushCallbacks函数,这个函数的作用就是用来执行callbacks回调数组的;callbacks.length=0
清空数组目的就是防止在nextTick中调用nextTick;
// 执行回调函数的函数
function flushCallbacks () {
pending = false
// 复制一份回调函数
const copies = callbacks.slice(0)
// 清空callbacks
callbacks.length = 0
// 遍历回调函数并且执行它
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
接下来定义了timerFunc变量,这个变量最终是一个函数,函数内部通过微任务或宏任务来异步执行flushCallbacks;从浏览器的兼容性首先选择微任务再选择宏任务,从promise到MutationObserver到setImmediate到setTimeout;
// promise存在通过promise执行回调
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
// 如果不是ie并且MutationObserver存在,就听过MutationObserver监听dom的变化,MutationObserver是异步的当dom变化完成之后才会执行它的回调,MutationObserver是微任务
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
// 修改dom中的内容,触发MutationObserver中的回调
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
// 如果setImmediate存在就通过setImmediate宏任务执行flushCallbacks
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {// 如果以上都不存在,就通过setTimeout执行flushCallbacks
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
再看看nextTick函数
// 源码位置 /src/core/util/next-tick.js
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// 把回调放在一个函数中执行,并且把这个函数存入到callbacks数组中
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
// 如果没有执行过,就执行
if (!pending) {
// 标记为执行过
pending = true
// 执行timerFunc
timerFunc()
}
// $flow-disable-line
// 如果没有传递回调函数,就返回一个Promise
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
首先通过callbacks存储传递进来的回调函数
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
如果pending为false,表示处于未执行状态,那么就执行timerFunc异步任务;并且标记pending为ture,防止重复执行;
// 如果没有执行过,就执行
if (!pending) {
// 标记为执行过
pending = true
// 执行timerFunc
timerFunc()
}
最后如果没有传递回调函数,那么直接返回一个promise对象
/ 如果没有传递回调函数,就返回一个Promise
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
// 对应的使用方法
this.$nextTick().then()
$destroy
/*
作用: 完全销毁一个实例,清理它与其它实例的链接,解绑它的全部指令和事件监听器
触发beforeDestroy和destroy钩子
*/
该方法的解析在生命周期篇的销毁阶段已经解析过;
1. 执行beforeDestory钩子
2. 从父级中删除当前实例
3. 从使用到当前watcher的数据中删除当前watcher
4. 从当前watcher中删除到它所依赖的其他watcher;
5. 移除响应式的引用
6. 删除当前组件的虚拟节点
7. 执行destroyed钩子
8. 删除当前组件的所有事件
Vue.prototype.$destroy = function () {
const vm: Component = this
// 判断是否正在被销毁,如果是就直接返回
if (vm._isBeingDestroyed) {
return
}
// 执行beforeDestroy钩子
callHook(vm, 'beforeDestroy')
// 设置正在被销毁
vm._isBeingDestroyed = true
// remove self from parent
// 获取到当前实例的父级
const parent = vm.$parent
// 如果父级存在并且父级没有被销毁并且不是抽象组件,把当前实例从父级上进行删除
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// teardown watchers
// 其他数据所依赖的当前实例的watcher进行删除
if (vm._watcher) {
vm._watcher.teardown()
}
// 把当前实例所依赖的其他watcher进行删除
let i = vm._watchers.length
while (i--) {
vm._watchers[i].teardown()
}
// remove reference from data ob
// frozen object may not have observer.
// 移除实例内响应式数据的引用
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--
}
// 给当前实例上添加_isDestroyed属性来表示当前实例已经被销毁
vm._isDestroyed = true
// invoke destroy hooks on current rendered tree
// 将实例的VNode树设置为null
vm.__patch__(vm._vnode, null)
// fire destroyed hook
// 执行destoryed钩子
callHook(vm, 'destroyed')
// turn off all instance listeners.
// 关闭事件监听
vm.$off()
// remove __vue__ reference
// __vue__指向null
if (vm.$el) {
vm.$el.__vue__ = null
}
// release circular reference (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
转载自:https://juejin.cn/post/7236771018609983548