一、为什么用nextTick
(1)js执行原理Eventloop
首先js是单线程的,所谓单线程,就是同一时间只能处理一件事情。JS中的任务分为同步任务和异步任务,其中异步任务分为宏任务和微任务。
所有同步任务都在主线程上执行,形成一个执行栈。而异步任务则会形成任务队列,宏任务进入宏队列,微任务进入微队列。
执行顺序:
①执行同步代码;
②等待所有的同步任务执行完毕,执行栈清空
③开始读取任务队列的任务,先从微队列取队首任务放入执行栈中执行
④继续取直到微队列任务完毕,如果执行过程中又产生了微任务则加入队列末尾,这个任务也会在这个周期执行
⑤微队列和执行栈都为空,则取宏队列队首任务放入栈中执行
⑥执行完毕,执行栈为空,重复③-⑤
(2)Vue数据驱动视图更新
vue 采用的异步更新策略,当监听到数据发生变化的时候不会立即去更新DOM,而是开启一个任务队列,并缓存在同一事件循环中发生的所有数据变更;这种做法带来的好处就是可以将多次数据更新合并成一次,减少操作DOM的次数从而减少性能的消耗。
nextTick 的本质是为了利用 JavaScript 的异步回调任务队列来实现 Vue 框架中自己的异步回调队列。
二、nextTick作用
nextTick 接收一个回调函数作为参数,并将这个回调函数延迟到DOM更新后才执行;
使用场景:想要操作基于最新数据的生成DOM 时,就将这个操作放在 nextTick 的回调中。
比如,动态生成文本框,实现自动聚焦功能;引入swiper库,需要等挂载的DOM生成后再生成swiper对象。
三、nextTick实现原理
将传入的回调函数包装成异步任务,nextTick 提供了四种异步方法 ,因为微任务优先于宏任务执行,所以优先级为
Promise.then > MutationObserver > setImmediate > setTimeOut(fn,0)
源码:
import { noop } from 'shared/util' import { handleError } from './error' import { isIE, isIOS, isNative } from './env' // noop 表示一个无操作空函数,用作函数默认值,防止传入 undefined 导致报错 // handleError 错误处理函数 // isIE, isIOS, isNative 环境判断函数, // isNative 判断是否原生支持,如果通过第三方实现支持也会返回 false export let isUsingMicroTask = false // nextTick 最终是否以微任务执行 const callbacks = [] // 存放调用 nextTick 时传入的回调函数 let pending = false // 标识当前是否有 nextTick 在执行,同一时间只能有一个执行 // 声明 nextTick 函数,接收一个回调函数和一个执行上下文作为参数 export function nextTick(cb?: Function, ctx?: Object) { let _resolve // 将传入的回调函数存放到数组中,后面会遍历执行其中的回调 callbacks.push(() => { if (cb) { // 对传入的回调进行 try catch 错误捕获 try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) // 如果当前没有在 pending 的回调,就执行 timeFunc 函数选择当前环境优先支持的异步方法 if (!pending) { pending = true timerFunc() } // 如果没有传入回调,并且当前环境支持 promise,就返回一个 promise if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } } // 判断当前环境优先支持的异步方法,优先选择微任务 // 优先级:Promise---> MutationObserver---> setImmediate---> setTimeout // setTimeOut 最小延迟也要4ms,而 setImmediate 会在主线程执行完后立刻执行 // setImmediate 在 IE10 和 node 中支持 // 多次调用 nextTick 时 ,timerFunc 只会执行一次 let timerFunc // 判断当前环境是否支持 promise if (typeof Promise !== 'undefined' && isNative(Promise)) { // 支持 promise const p = Promise.resolve() timerFunc = () => { // 用 promise.then 把 flushCallbacks 函数包裹成一个异步微任务 p.then(flushCallbacks) if (isIOS) setTimeout(noop) } // 标记当前 nextTick 使用的微任务 isUsingMicroTask = true // 如果不支持 promise,就判断是否支持 MutationObserver // 不是IE环境,并且原生支持 MutationObserver,那也是一个微任务 } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]' )) { let counter = 1 // new 一个 MutationObserver 类 const observer = new MutationObserver(flushCallbacks) // 创建一个文本节点 const textNode = document.createTextNode(String(counter)) // 监听这个文本节点,当数据发生变化就执行 flushCallbacks observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) // 数据更新 } isUsingMicroTask = true // 标记当前 nextTick 使用的微任务 // 判断当前环境是否原生支持 setImmediate } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { timerFunc = () => { setImmediate(flushCallbacks) } } else { // 以上三种都不支持就选择 setTimeout timerFunc = () => { setTimeout(flushCallbacks, 0) } } // 如果多次调用 nextTick,会依次执行上面的方法,将 nextTick 的回调放在 callbacks 数组中 // 最后通过 flushCallbacks 函数遍历 callbacks 数组的拷贝并执行其中的回调 function flushCallbacks() { pending = false const copies = callbacks.slice(0) // 拷贝一份 callbacks.length = 0 // 清空 callbacks for (let i = 0; i < copies.length; i++) { // 遍历执行传入的回调 copies[i]() } } // callbacks.slice(0) 将 callbacks 拷贝出来一份, // 是因为考虑到 nextTick 回调中可能还会调用 nextTick 的情况, // 如果 nextTick 回调中又调用了一次 nextTick,则又会向 callbacks 中添加回调, // nextTick 回调中的 nextTick 应该放在下一轮执行, // 如果不将 callbacks 复制一份就可能一直循环
参考:https://blog.csdn.net/web220507/article/details/125141403
标签:nextTick,异步,回调,学习,callbacks,任务,原理,执行 From: https://www.cnblogs.com/cxuep/p/16714217.html