认识Vue中的nextTick
有时候有需求,需要根据动态数据去为页面某些DOM元素添加事件,这就需要在DOM更新完毕之后去设置,但是created与mounted函数执行时一般dom并没有渲染完毕,所以就会出现获取不到,添加不了事件的问题,这就要用到nextTick处理了。
Js是单线程语言,它是基于事件循环,事件循环大致分为以下几个步骤:
-
主线程之外, 还存在⼀个”任务队列“(task queue) 。 只要异步任务有了运⾏结果, 就在”任务队列”之中放置⼀个事件。
-
一旦”执⾏栈”中的所有同步任务执⾏完毕, 系统就会读取“任务队列”, 看看⾥⾯有哪些事件。 那些对应的异步任务, 于是结束等待状态, 进⼊执⾏栈, 开始执⾏。
nextTick的源码位于 node_mondules/vue/src/core/util/next–tick.ts,总计118行,十分的短小精悍,去掉注释也就不到80行的代码,十分适合初次阅读源码的同学。
-
环境检测(根据环境检测以不同的方式执行回调队列)众所周知,Event Loop分为宏任务(macro task)以及微任务( micro task),不管执行宏任务还是微任务,完成后都会进入下一个tick,但是宏任务的执行时间要大于微任务,所以在浏览器支持的情况下优先使用微任务队列,浏览器不支持微任务的情况下才去使用宏任务队列,但是在各种宏微任务之间,也有效率的不同,所以要根据浏览器的支持情况,去创建不同的宏微任务,nextTick在环境检测这块,就是遵循的这种行为。
let timerFunc // 判断是否支持 Promise 浏览器是否支持原生的setImmediate(setImmediate只在IE中有效) if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) if (isIOS) setTimeout(noop) } isUsingMicroTask = true //判断是否支持 MutationObserver } else if ( !isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]') ) { let counter = 1 const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true // } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { timerFunc = () => { setImmediate(flushCallbacks) } // 都不支持的情况下,使用setTimeout } else { timerFunc = () => { setTimeout(flushCallbacks, 0) } }
-
// 回调函数队列 const callbacks = [] let pending = false function flushCallbacks() { pending = false // 防止出现nextTick中包含nextTick时出现问题,在执行回调函数队列前,提前复制备份,清空回调函数队列 const copies = callbacks.slice(0) callbacks.length = 0 // 执行函数队列 for (let i = 0; i < copies.length; i++) { copies[i]() } } // 我们调用的nextTick函数 export function nextTick(cb?: (...args: any[]) => any, ctx?: object) { let _resolve // 将我们调用nextTick函数传入的回调函数推入异步回调队列中 callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e: any) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true timerFunc() } // 如果没有传入回调,并且支持Promise 返回一个新的Promise if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
四.总结
总体流程就是nextTick接收一个回调函数,将回调函数推入回调队列中,同时,在接收第一个回调函数时,环境检测中对应的异步方法(异步方法中调用了回调函数队列)。我们自己简单实现一个nextTick:
let callbacks = [] let pending = false function nextTick(callback) { if (callback) { callbacks.push(callback) } if (!pending) { pending = true setTimeout(flushCallbacks(), 0) } } function flushCallbacks() { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }
可以看到在我们实现的简易版的nextTick中,通过nextTick接收回调函数,通过setTimeout来异步执行回调函数。通过这种方式,可以实现在下一个tick中执行回调函数,即在DOM更新后执行回调函数。
原文地址:https://blog.csdn.net/weixin_55034688/article/details/127126058
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_16331.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!