使用场景
- 想要操作基于最新数据生成的Dom,就需要使用nextTick
onSubmit(formName: string) {
(this.$refs[formName] as Form).validate((valid) => {
if (valid && this.validateRateTotal()) {
this.$emit('showSplitterModel', this.splitterForm, 'success');
} else {
this.$nextTick(() => {
const errorElement = document.getElementsByClassName(
'el-form-item__error'
)[0] as HTMLElement;
errorElement.scrollIntoView({ block: 'center' });
});
}
});
}
前置知识
-
nextTick的作用相当于异步执行传入的函数
-
同步任务:js是单线程的,单线程就是事件任务的执行要排队,前一个任务结束,才能进入下一个任务
-
步骤:
- 所有的同步任务都在主线程执行,形成一个执行栈
- 除了主线程之外,还有一个任务队列;一旦异步任务有了运行结果,就把其回调函数作为一个任务放到任务队列中
- 一旦执行栈中的所有同步任务完成,就会读取任务队列,将里面的任务添加到执行栈中执行
- 主线程不断的重复第三步
-
异步任务
- 异步任务的执行都是通过任务队列来执行的。异步任务分类两大类:宏任务和微任务(参考文章)
- 宏任务:setTimeout、setInterval、postMessage、MessageChannel、 网络请求IO、DOM、鼠标、键盘、滚动事件、页面渲染
- 微任务:Promise.then、 MutationObserve、process.nexttick
-
实现原理
1. 将传入的回调函数包装成异步任务,异步任务又分为微任务和宏任务。为了尽快执行所以优先选择微任务(why:按照事件循环的执行顺序,执行下一次宏任务之前会进行一次UI渲染,等待的时间会比微任务多的多)
2. 提供四种异步方法:promise.then、mutationObserver、setImmediate、setTinmeout(fn, 0)
3. 主流程:将传入的回调函数包装一下存入callbacks数组中,调用timerFunc函数,在其中去遍历执行callbacks函数,这样就实现了nextTick函数异步执行传入函数
4. 源码解读 -
将传入的回调函数处理一下,放入callbacks数组
-
利用pending来保证执行一个事件循环只执行一次timerFunc
-
最后判断一下是否传入了回调函数且支持Promise
-
优先执行promise。Promise是个微任务,因此timerFunc就变成了异步执行
-
若不支持Promise,在执行MutationObserver。让counter的值在0和1之间变化,再把变化的counter设置为文本节点的内容,这样observer就会监测到文本节点的变化,就会调用flushCallbacks。MutationObserver是个微任务,因此timerFunc就变成了异步执行
-
若不支持MutationObserver,在执行setImmediate。只兼容IE10以上的浏览器,其他浏览器不支持,宏任务,资源消耗小
-
若不支持setImmediate,在执行setTimeout。兼容IE10以下,宏任务,资源消耗大
-
判断是否原生支持;如果浏览器内置函数调用实例方法的话,就会返回一个native code
-
flushCallbacks函数遍历callbacks数组的拷贝并执行其中的回调
-
为什么要拷贝callbacks:nextTick回调中还有可能会调用nextTick,就有可能向callbacks中添加回调,就有可能一直循环。nextTick回调中的nextTick应该放在下一次的循环里面
-