关于Vue中$nextTick的作用及实现原理(Vue进阶)
Hello,大家好,最近找工作的路途依然艰难,但是要一直保持对技术的热爱!今天继续跟大家探索一下Vue进阶的内容,这次研究一下Vue的全局API中的$nextTick,这是一个很常用,也是经典的API。
PS: 哪位大佬公司还缺前端,能帮忙内推的,可以加下我联系方式,22届应届生,有6个多月实习经验,PC、小程序、App均有若干项目实践,base地点不限,一线城市均可。
$nextTick的作用
相信经常使用Vue
框架开发项目的同学一定对这个API
不陌生。如果有同学没有用过,我们拿一个小demo看一下使用过程。
<template><div id="example">{{message}}</div>
</template>
<script>var vm = new Vue({el: '##example',data: {message: '123'}})vm.message = 'new message' // 更改数据console.log(vm.$el.innerHTML) // '123'Vue.nextTick(function () {console.log(vm.$el.innerHTML) // 'new message'})
</script>
这是一段非常简单的代码,我们首先动态渲染了一个message
变量,它的初始值为'123'
,我们想将message
的值修改为'new message'
。但是,我们修改完message
变量的值后,立刻输出它的DOM
值,可以发现,message
依然为'123'
。但是从实际的渲染视图来看,message
确实在视图上由'123'
改为了'new message'
。
那这是怎么回事的,回想一下我们的代码,其实就是通过Vue
框架做了一个最简单的DOM
更新操作。既然涉及到了DOM
更新,就需要了解Vue
更新DOM
的机制,Vue
内部维护了一个虚拟DOM
,我们进行常规的DOM
操作,并不是立刻更新真实的DOM
树,而是被Vue
记录在了内部的虚拟DOM
上,然后再统一进行更新,这个统一更新的操作是异步的,Vue
内部维护了一个任务队列。
所有,为什么我们修改完DOM
的值后,立刻输出这个DOM
的值还是原来的值,因为此时这个DOM
更新的操作被Vue
记录了下来,存到了需要更新的任务队列里,等待更新。
那么怎么解决这个问题呢,Vue
为我们提供了一个全局API
,$nextTick
,它支持传入一个回调函数,只有当Vue
的DOM
操作更新结束之后,才会执行这个回调函数,所以,在传入的回调函数中输出DOM
的值,一定是更新之后的结果。
那么,$nextTick
是如何实现的呢?
$nextTick的实现原理
想要知道$nextTick
的原理就要先弄明白,Vue
是如何维护一个内部的任务队列来异步更新DOM
的。
牵扯的知识点其实越来越多,我们首先要充分了解JS
的运行机制。我们知道JS
执行的单线程的,它能实现高效执行、不阻塞,是基于了事件循环的机制。
我简单概述一下事件循环的过程,首先JS
中所有的代码先被分为了同步任务和异步任务,代码的执行有一个主的执行栈,同步代码从上到下依次执行,异步代码会被怼到任务队列里,任务队列中的异步代码再次被分为了宏任务和微任务,宏任务和微任务的执行原则就是,优先执行微任务队列的代码,微任务队列清空之后,再去执行宏任务队列,每执行完一个宏任务都要去清空一遍微任务队列(前提是有)。
上次宏任务和微任务的执行原则可以大概用以下代码概述。
for (macroTask of macroTaskQueue) {// 1. 处理当前的宏任务handleMacroTask();// 2. 处理对应的所有微任务for (microTask of microTaskQueue) {handleMicroTask(microTask);}
}
在浏览器环境中,常见的宏任务和微任务有:
- 宏任务(
macro task
)有setTimeout
、MessageChannel
、postMessage
、setImmediate
- 微任务(
micro task
)有MutationObsever
和Promise.then
了解了关于JS
的事件循环后,回到Vue
中,Vue
内部其实也需要维护一个异步更新的任务队列,那么最好的办法就是借鉴JS
原生提供的能力。
Vue
在内部对异步队列尝试使用原生的Promise.then
、MutationObserver
和setImmediate
,如果执行环境不支持,则会采用setTimeout
代替。
宏任务耗费的时间是大于微任务的,所以在浏览器支持的情况下,优先使用微任务。如果浏览器不支持微任务,使用宏任务;但是,各种宏任务之间也有效率的不同,需要根据浏览器的支持情况,使用不同的宏任务。
我们看一下Vue
源码中关于这部分功能的实现:
位置 src/core/util/next-tick.js
中
let microTimerFunc
let macroTimerFunc
let useMacroTask = false/* 对于宏任务(macro task) */
// 检测是否支持原生 setImmediate(高版本 IE 和 Edge 支持)
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {macroTimerFunc = () => {setImmediate(flushCallbacks)}
}
// 检测是否支持原生的 MessageChannel
else if (typeof MessageChannel !== 'undefined' && (isNative(MessageChannel) ||// PhantomJSMessageChannel.toString() === '[object MessageChannelConstructor]'
)) {const channel = new MessageChannel()const port = channel.port2channel.port1.onmessage = flushCallbacksmacroTimerFunc = () => {port.postMessage(1)}
}
// 都不支持的情况下,使用setTimeout
else {macroTimerFunc = () => {setTimeout(flushCallbacks, 0)}
}/* 对于微任务(micro task) */
// 检测浏览器是否原生支持 Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {const p = Promise.resolve()microTimerFunc = () => {p.then(flushCallbacks)}
}
// 不支持的话直接指向 macro task 的实现。
else {// fallback to macromicroTimerFunc = macroTimerFunc
}
首先声明了两个变量: microTimerFunc
和 macroTimerFunc
,它们分别对应的是 micro task
的函数和 macro task
的函数。对于 macro task
的实现,优先检测是否支持原生 setImmediate
,这是一个高版本 IE
和Edge
才支持的特性,不支持的话再去检测是否支持原生的 MessageChannel
,如果也不支持的话就会降级为 setTimeout
;而对于 micro task
的实现,则检测浏览器是否原生支持 Promise
,不支持的话直接指向 macro task
的实现。
OK,有了以上的铺垫,我们的主角$nextTick
来了,以下是它的核心代码。
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]()}
}export function nextTick (cb?: Function, ctx?: Object) {let _resolve// 将回调函数推入回调队列callbacks.push(() => {if (cb) {try {cb.call(ctx)} catch (e) {handleError(e, ctx, 'nextTick')}} else if (_resolve) {_resolve(ctx)}})// 如果异步锁未锁上,锁上异步锁,调用异步函数,准备等同步函数执行完后,就开始执行回调函数队列if (!pending) {pending = trueif (useMacroTask) {macroTimerFunc()} else {microTimerFunc()}}// 如果没有提供回调,并且支持Promise,返回一个Promiseif (!cb && typeof Promise !== 'undefined') {return new Promise(resolve => {_resolve = resolve})}
}
首先,先来看 nextTick
函数,该函数的主要逻辑是:先把传入的回调函数 cb
推入 回调队列callbacks
数组,同时在接收第一个回调函数时,执行能力检测中对应的异步方法(异步方法中调用了回调函数队列)。最后一次性地根据 useMacroTask
条件执行 macroTimerFunc
或者是 microTimerFunc
,而它们都会在下一个 tick
执行 flushCallbacks
,flushCallbacks
的逻辑非常简单,对 callbacks
遍历,然后执行相应的回调函数。
nextTick
函数最后还有一段逻辑:
if (!cb && typeof Promise !== 'undefined') {return new Promise(resolve => {_resolve = resolve})
}
这是当 nextTick
不传 cb
参数的时候,提供一个 Promise
化的调用,比如:
nextTick().then(() => {})
当 _resolve
函数执行,就会跳到 then
的逻辑中。
这里有两个问题需要注意:
- 如何保证只在接收第一个回调函数时执行异步方法?
nextTick
源码中使用了一个异步锁的概念,即接收第一个回调函数时,先关上锁,执行异步方法。此时,浏览器处于等待执行完同步代码就执行异步代码的情况。
- 执行
flushCallbacks
函数时为什么需要备份回调函数队列?执行的也是备份的回调函数队列?
因为,会出现这么一种情况:nextTick
的回调函数中还使用 nextTick
。如果 flushCallbacks
不做特殊处理,直接循环执行回调函数,会导致里面nextTick
中的回调函数会进入回调队列。
以上就是对 nextTick
的源码分析,我们了解到数据的变化到 DOM
的重新渲染是一个异步过程,发生在下一个 tick
。当我们在实际开发中,比如从服务端接口去获取数据的时候,数据做了修改,如果我们的某些方法去依赖了数据修改后的 DOM
变化,我们就必须在 nextTick
后执行。
总结
今天带着大家探索了一下Vue中的一个全局API
$nextTick
的基本用法和实现原理,牵扯出了很多的知识点,Vue
的虚拟DOM
、Vue
更新DOM
的机制、JS
的事件循环,跟随Vue的源码和大家共同学习一下。
QQ: 505417246
WX: 18331092918
公众号: Code程序人生
B站账号: LuckyRay123
个人博客: http://rayblog.ltd/
欢迎关注我的各类账号, 持续更新优质前端内容
关于Vue中$nextTick的作用及实现原理(Vue进阶)相关推荐
- 什么是Vue生命周期?Vue生命周期的作用是什么?vue八种钩子函数
1.vue生命周期介绍 vue生命周期是指vue对象从创建到销毁的过程.也就是vue对象从开始创建.初始化数据.编译模板.挂载Dom.渲染→更新→渲染.卸载这一系列过程. 其作用是在vue生命周期的不 ...
- Vue 曝光埋点插件的实现原理
VUE 曝光埋点插件的实现原理 vue自定义 插件 说到vue插件,vue就提供了自定义插件的功能. 定义 定义一个文件,这个文件 导出一个对象,这个对象包含install方法. install方法有 ...
- Vue使用nextTick的原因和作用
在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 DOM. 疑问: DOM 更新循环是指什么? 下次更新循环是什么时候? 修改数据之后使用,是加快了数据更新进 ...
- Vue中$nextTick的理解
Vue中$nextTick的理解 Vue中$nextTick方法将回调延迟到下次DOM更新循环之后执行,也就是在下次DOM更新循环结束之后执行延迟回调,在修改数据之后立即使用这个方法,能够获取更新后的 ...
- updated beforeUpdate() Updated() 生命周期-销毁阶段 vue的nextTick@stage3---week2--day4-1
updated && beforeUpdate() 和 Updated()钩子函数 && 生命周期-销毁阶段-外部销毁 && 生命周期-销毁阶段-内部销 ...
- VUE this.$nextTick()的使用场景
[TAG - 1]: 官网解释: 将回调延迟到下次 DOM 更新循环之后执行.在修改数据之后立即使用它,然后等待 DOM 更新. 它跟全局方法 Vue.nextTick 一样,不同的是回调的 this ...
- vue 之 nextTick 与$nextTick
VUE中Vue.nextTick()和this.$nextTick()怎么使用? 官方文档是这样解释的: 在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 D ...
- vue的$nextTick使用总结,this.$refs为undefined的解决办法,element Ui的树形控件setCheckedKeys方法无法使用
vue的$nextTick使用总结,this.$refs为undefined的解决办法,element Ui的树形控件setCheckedKeys方法无法使用 参考文章: (1)vue的$nextTi ...
- mockjs vue ajax,mockjs在vue中的使用
mockjs在vue中的使用 mockjs在vue中的使用 前后端分工协作是一个非常高效的做法,但是有时前后端分离不彻底会很痛苦.前后端应该是异步进行的,进度互不影响,但是在没有mock的时候,前端却 ...
最新文章
- matlab计算数字滤波器的幅频响应
- 如何开发auto complete 智能提示功能
- hdu 6386 Age of Moyu (重边判断)
- C语言进阶深度学习目录表
- Java Long类shortValue()方法与示例
- 【java】dubbo基础学习
- 最常见到的runtime exception 异常
- autohotkey循环
- catboost原理
- 程序员必学电脑计算机专业英语词汇 07 (142 单词)
- cesium实现动态立体墙效果
- 下列关于python运算符的使用描述正确的是_以下关于 Python 字符串的描述中,正确的是( )...
- java中jsp内建对象有_JSP内置对象有哪些
- Automa 和看小说脚本
- “Unable to create the directory [XXX] to use as the base directory”的解决办法
- Go const和iota 使用实战
- C++ 比赛时 无穷小、无穷大的设定
- Qt Mediaplayer videoplayer 例子工程 Media Player Example 应用过程中出现的问题(一)视频无法播放
- Spark LDA 主题抽取
- C语言 if.....else语句(双分支结构)