灵犀科技网站开发佼佼者,门户网站建设和检务公开整改,做微信公众号必备的网站,住房与城乡建设网站node 中的 nextTick
node 中的 nextTick 是 node 自带全局的变量 process 的一个方法#xff0c;process.nextTick 是一个微任务#xff0c;在 node 的所有微任务中最先执行#xff0c;是优先级最高的微任务。浏览器中是没有这一个方法的。
vue 中的 nextTick
vue 中的 n…node 中的 nextTick
node 中的 nextTick 是 node 自带全局的变量 process 的一个方法process.nextTick 是一个微任务在 node 的所有微任务中最先执行是优先级最高的微任务。浏览器中是没有这一个方法的。
vue 中的 nextTick
vue 中的 nextTick 和 node 中的完全不同的东西是 vue 源码中有自己的实现方法而且 vue2 和 vue3 中的实现方法还不同。作用是在下次 DOM 更新循环结束之后执行延迟回调
在下次 DOM 更新循环结束之后执行延迟回调
vue2 中的 nextTick 实现方法
在 vue2 源码中有一个专门的文件用来实现 nextTick 方法可以自己去看一下他依次判断并使用了 promise、mutationObserver、setImmediate、setTimeout 来调用回调函数
源代码如下
/* globals MutationObserver */import { noop } from shared/util
import { handleError } from ./error
import { isIE, isIOS, isNative } from ./envexport let isUsingMicroTask falseconst callbacks: ArrayFunction []
let pending falsefunction flushCallbacks() {pending falseconst copies callbacks.slice(0)callbacks.length 0for (let i 0; i copies.length; i) {copies[i]()}
}// Here we have async deferring wrappers using microtasks.
// In 2.5 we used (macro) tasks (in combination with microtasks).
// However, it has subtle problems when state is changed right before repaint
// (e.g. #6813, out-in transitions).
// Also, using (macro) tasks in event handler would cause some weird behaviors
// that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109).
// So we now use microtasks everywhere, again.
// A major drawback of this tradeoff is that there are some scenarios
// where microtasks have too high a priority and fire in between supposedly
// sequential events (e.g. #4521, #6690, which have workarounds)
// or even between bubbling of the same event (#6566).
let timerFunc// The nextTick behavior leverages the microtask queue, which can be accessed
// via either native Promise.then or MutationObserver.
// MutationObserver has wider support, however it is seriously bugged in
// UIWebView in iOS 9.3.3 when triggered in touch event handlers. It
// completely stops working after triggering a few times... so, if native
// Promise is available, we will use it:
/* istanbul ignore next, $flow-disable-line */
if (typeof Promise ! undefined isNative(Promise)) {const p Promise.resolve()timerFunc () {p.then(flushCallbacks)// In problematic UIWebViews, Promise.then doesnt completely break, but// it can get stuck in a weird state where callbacks are pushed into the// microtask queue but the queue isnt being flushed, until the browser// needs to do some other work, e.g. handle a timer. Therefore we can// force the microtask queue to be flushed by adding an empty timer.if (isIOS) setTimeout(noop)}isUsingMicroTask true
} else if (!isIE typeof MutationObserver ! undefined (isNative(MutationObserver) ||// PhantomJS and iOS 7.xMutationObserver.toString() [object MutationObserverConstructor])
) {// Use MutationObserver where native Promise is not available,// e.g. PhantomJS, iOS7, Android 4.4// (#6466 MutationObserver is unreliable in IE11)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)) {// Fallback to setImmediate.// Technically it leverages the (macro) task queue,// but it is still a better choice than setTimeout.timerFunc () {setImmediate(flushCallbacks)}
} else {// Fallback to setTimeout.timerFunc () {setTimeout(flushCallbacks, 0)}
}export function nextTick(): Promisevoid
export function nextTickT(this: T, cb: (this: T, ...args: any[]) any): void
export function nextTickT(cb: (this: T, ...args: any[]) any, ctx: T): void
/*** internal*/
export function nextTick(cb?: (...args: any[]) any, ctx?: object) {let _resolvecallbacks.push(() {if (cb) {try {cb.call(ctx)} catch (e: any) {handleError(e, ctx, nextTick)}} else if (_resolve) {_resolve(ctx)}})if (!pending) {pending truetimerFunc()}// $flow-disable-lineif (!cb typeof Promise ! undefined) {return new Promise(resolve {_resolve resolve})}
}
vue3 中 nextTick 实现方法
vue3 中使用 promise 来实现nextTick 方法返回一个 Promise 对象因此可以使用 Promise 的链式调用或 async/await 语法来处理 nextTick 回调。在源码的scheduler.ts 文件中定义
完整代码如下
import { ErrorCodes, callWithErrorHandling, handleError } from ./errorHandling
import { type Awaited, NOOP, isArray } from vue/shared
import { type ComponentInternalInstance, getComponentName } from ./componentexport interface SchedulerJob extends Function {id?: numberpre?: booleanactive?: booleancomputed?: boolean/*** Indicates whether the effect is allowed to recursively trigger itself* when managed by the scheduler.** By default, a job cannot trigger itself because some built-in method calls,* e.g. Array.prototype.push actually performs reads as well (#1740) which* can lead to confusing infinite loops.* The allowed cases are component update functions and watch callbacks.* Component update functions may update child component props, which in turn* trigger flush: pre watch callbacks that mutates state that the parent* relies on (#1801). Watch callbacks doesnt track its dependencies so if it* triggers itself again, its likely intentional and it is the users* responsibility to perform recursive state mutation that eventually* stabilizes (#1727).*/allowRecurse?: boolean/*** Attached by renderer.ts when setting up a components render effect* Used to obtain component information when reporting max recursive updates.* dev only.*/ownerInstance?: ComponentInternalInstance
}export type SchedulerJobs SchedulerJob | SchedulerJob[]let isFlushing false
let isFlushPending falseconst queue: SchedulerJob[] []
let flushIndex 0const pendingPostFlushCbs: SchedulerJob[] []
let activePostFlushCbs: SchedulerJob[] | null null
let postFlushIndex 0const resolvedPromise /*#__PURE__*/ Promise.resolve() as Promiseany
let currentFlushPromise: Promisevoid | null nullconst RECURSION_LIMIT 100
type CountMap MapSchedulerJob, numberexport function nextTickT void, R void(this: T,fn?: (this: T) R,
): PromiseAwaitedR {const p currentFlushPromise || resolvedPromisereturn fn ? p.then(this ? fn.bind(this) : fn) : p
}// #2768
// Use binary-search to find a suitable position in the queue,
// so that the queue maintains the increasing order of jobs id,
// which can prevent the job from being skipped and also can avoid repeated patching.
function findInsertionIndex(id: number) {// the start index should be flushIndex 1let start flushIndex 1let end queue.lengthwhile (start end) {const middle (start end) 1const middleJob queue[middle]const middleJobId getId(middleJob)if (middleJobId id || (middleJobId id middleJob.pre)) {start middle 1} else {end middle}}return start
}export function queueJob(job: SchedulerJob) {// the dedupe search uses the startIndex argument of Array.includes()// by default the search index includes the current job that is being run// so it cannot recursively trigger itself again.// if the job is a watch() callback, the search will start with a 1 index to// allow it recursively trigger itself - it is the users responsibility to// ensure it doesnt end up in an infinite loop.if (!queue.length ||!queue.includes(job,isFlushing job.allowRecurse ? flushIndex 1 : flushIndex,)) {if (job.id null) {queue.push(job)} else {queue.splice(findInsertionIndex(job.id), 0, job)}queueFlush()}
}function queueFlush() {if (!isFlushing !isFlushPending) {isFlushPending truecurrentFlushPromise resolvedPromise.then(flushJobs)}
}export function invalidateJob(job: SchedulerJob) {const i queue.indexOf(job)if (i flushIndex) {queue.splice(i, 1)}
}export function queuePostFlushCb(cb: SchedulerJobs) {if (!isArray(cb)) {if (!activePostFlushCbs ||!activePostFlushCbs.includes(cb,cb.allowRecurse ? postFlushIndex 1 : postFlushIndex,)) {pendingPostFlushCbs.push(cb)}} else {// if cb is an array, it is a component lifecycle hook which can only be// triggered by a job, which is already deduped in the main queue, so// we can skip duplicate check here to improve perfpendingPostFlushCbs.push(...cb)}queueFlush()
}export function flushPreFlushCbs(instance?: ComponentInternalInstance,seen?: CountMap,// if currently flushing, skip the current job itselfi isFlushing ? flushIndex 1 : 0,
) {if (__DEV__) {seen seen || new Map()}for (; i queue.length; i) {const cb queue[i]if (cb cb.pre) {if (instance cb.id ! instance.uid) {continue}if (__DEV__ checkRecursiveUpdates(seen!, cb)) {continue}queue.splice(i, 1)i--cb()}}
}export function flushPostFlushCbs(seen?: CountMap) {if (pendingPostFlushCbs.length) {const deduped [...new Set(pendingPostFlushCbs)].sort((a, b) getId(a) - getId(b),)pendingPostFlushCbs.length 0// #1947 already has active queue, nested flushPostFlushCbs callif (activePostFlushCbs) {activePostFlushCbs.push(...deduped)return}activePostFlushCbs dedupedif (__DEV__) {seen seen || new Map()}for (postFlushIndex 0;postFlushIndex activePostFlushCbs.length;postFlushIndex) {if (__DEV__ checkRecursiveUpdates(seen!, activePostFlushCbs[postFlushIndex])) {continue}activePostFlushCbs[postFlushIndex]()}activePostFlushCbs nullpostFlushIndex 0}
}const getId (job: SchedulerJob): number job.id null ? Infinity : job.idconst comparator (a: SchedulerJob, b: SchedulerJob): number {const diff getId(a) - getId(b)if (diff 0) {if (a.pre !b.pre) return -1if (b.pre !a.pre) return 1}return diff
}function flushJobs(seen?: CountMap) {isFlushPending falseisFlushing trueif (__DEV__) {seen seen || new Map()}// Sort queue before flush.// This ensures that:// 1. Components are updated from parent to child. (because parent is always// created before the child so its render effect will have smaller// priority number)// 2. If a component is unmounted during a parent components update,// its update can be skipped.queue.sort(comparator)// conditional usage of checkRecursiveUpdate must be determined out of// try ... catch block since Rollup by default de-optimizes treeshaking// inside try-catch. This can leave all warning code unshaked. Although// they would get eventually shaken by a minifier like terser, some minifiers// would fail to do that (e.g. https://github.com/evanw/esbuild/issues/1610)const check __DEV__? (job: SchedulerJob) checkRecursiveUpdates(seen!, job): NOOPtry {for (flushIndex 0; flushIndex queue.length; flushIndex) {const job queue[flushIndex]if (job job.active ! false) {if (__DEV__ check(job)) {continue}callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)}}} finally {flushIndex 0queue.length 0flushPostFlushCbs(seen)isFlushing falsecurrentFlushPromise null// some postFlushCb queued jobs!// keep flushing until it drains.if (queue.length || pendingPostFlushCbs.length) {flushJobs(seen)}}
}function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob) {if (!seen.has(fn)) {seen.set(fn, 1)} else {const count seen.get(fn)!if (count RECURSION_LIMIT) {const instance fn.ownerInstanceconst componentName instance getComponentName(instance.type)handleError(Maximum recursive updates exceeded${componentName ? in component ${componentName} : }. This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.,null,ErrorCodes.APP_ERROR_HANDLER,)return true} else {seen.set(fn, count 1)}}
}