nextTick的使用背景
- 在vue项目中,经常会使用到nextTick这个api,一直在猜想其是怎么实现的,今天有幸研读了下,虽然源码又些许问题,但仍值得借鉴
核心源码解析
判断当前环境使用最合适的API并保存函数
promise
- 判断是否支持promise,如果支持就使用Promise对象的then方法包裹要执行的 flushCallbacks函数
MutationObserver
- 判断是否支持MutationObserver,如果支持就创建一个MutationObserver 用于监听dom改动之后执行 flushCallbacks 函数 并赋值给 observer
setImmediate
- 判断是否支持setImmediate,如果支持就使用setImmediate包裹 flushCallbacks函数
setTimeout
- 如果以上三种都不支持使用setTimeout包裹 flushCallbacks函数
export let isUsingMicroTask = falseconst callbacks = []
let pending = falsefunction flushCallbacks() {pending = falseconst copies = callbacks.slice(0)callbacks.length = 0for (let i = 0; i < copies.length; i++) {copies[i]()}
}let timerFuncif (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) ||MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1const observer = new MutationObserver(flushCallbacks)const textNode = document.createTextNode(String(counter))observer.observe(textNode, {characterData: true})timerFunc = () => {counter = (counter + 1) % 2textNode.data = String(counter)}isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {setImmediate(flushCallbacks)}
} else {
timerFunc = () => {setTimeout(flushCallbacks, 0)}
}
调用异步函数执行回调对列
入参分析
nextTick(cb?: Function, ctx?: Object) {}
- cb是 传入的回调函数
- ctx是函数执行的上下文
- 而者都又是可选参数, 但是有问题 下文有解析
函数执行逻辑
- 将调用nextTick是传入的执行函数添加到 callbacks中
- 可使用call和传入的ctx修改传入的执行函数的this指向
export function nextTick(cb?: Function, ctx?: Object) {let _resolvecallbacks.push(() => {if (cb) {try {cb.call(ctx)} catch (e) {handleError(e, ctx, 'nextTick')}} })if (!pending) {pending = truetimerFunc()}
}
- 这个代码有删减,因为其余代码不会执行是 伪代码 下文有解析
伪代码分析
- nexttick的参数中cb不能为可选参数,如果cb参数不传将没有回调函数,nextTick将没有意义,并且ctx将成为第一个参数,由于是形参,ctx将顶替cb但是ctx不是函数类型,就会抛错
依次执行nextTick
function flushCallbacks() {pending = falseconst copies = callbacks.slice(0)callbacks.length = 0for (let i = 0; i < copies.length; i++) {copies[i]()}
}
源码
import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
export let isUsingMicroTask = falseconst callbacks = []
let pending = falsefunction flushCallbacks() {pending = falseconst copies = callbacks.slice(0)callbacks.length = 0for (let i = 0; i < copies.length; i++) {copies[i]()}
}let timerFuncif (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) ||MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1const observer = new MutationObserver(flushCallbacks)const textNode = document.createTextNode(String(counter))observer.observe(textNode, {characterData: true})timerFunc = () => {counter = (counter + 1) % 2textNode.data = String(counter)}isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {setImmediate(flushCallbacks)}
} else {
timerFunc = () => {setTimeout(flushCallbacks, 0)}
}export function nextTick(cb?: Function, ctx?: Object) {let _resolvecallbacks.push(() => {if (cb) {try {cb.call(ctx)} catch (e) {handleError(e, ctx, 'nextTick')}} else if (_resolve) {console.log('ctx')_resolve(ctx)}})if (!pending) {pending = truetimerFunc()}if (!cb && typeof Promise !== 'undefined') {return new Promise(resolve => {_resolve = resolve})}
}
个人困惑
- 本人实在是不太理解下列代码的意义是什么,感觉是没用的,但是不太确定,以下是个人分析
分析
- 首先将执行上下文this抛出是没什么意义的
- 其次promise的resolve 单独拿出来在此处有什么作用呢
let _resolveif (!cb && typeof Promise !== 'undefined') {return new Promise(resolve => {_resolve = resolve})}_resolve(ctx)
致谢
- 感谢您百忙之中抽时间阅读我写的博客,谢谢你的肯定,也希望对您能有所帮助
- 如果您有更好的见解请在评论区留言或者私聊我,期待与您的交流