vue源码解析之实例方法

您所在的位置:网站首页 vue将object转为数组 vue源码解析之实例方法

vue源码解析之实例方法

2023-05-27 16:31| 来源: 网络整理| 查看: 265

上一篇分析了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 | 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 | 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;

image.png

$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, 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, 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 } }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3