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,它支持传入一个回调函数,只有当VueDOM操作更新结束之后,才会执行这个回调函数,所以,在传入的回调函数中输出DOM的值,一定是更新之后的结果。

那么,$nextTick是如何实现的呢?

$nextTick的实现原理

想要知道$nextTick的原理就要先弄明白,Vue是如何维护一个内部的任务队列来异步更新DOM的。

牵扯的知识点其实越来越多,我们首先要充分了解JS的运行机制。我们知道JS执行的单线程的,它能实现高效执行、不阻塞,是基于了事件循环的机制。

我简单概述一下事件循环的过程,首先JS中所有的代码先被分为了同步任务和异步任务,代码的执行有一个主的执行栈,同步代码从上到下依次执行,异步代码会被怼到任务队列里,任务队列中的异步代码再次被分为了宏任务和微任务,宏任务和微任务的执行原则就是,优先执行微任务队列的代码,微任务队列清空之后,再去执行宏任务队列,每执行完一个宏任务都要去清空一遍微任务队列(前提是有)。

上次宏任务和微任务的执行原则可以大概用以下代码概述。

for (macroTask of macroTaskQueue) {// 1. 处理当前的宏任务handleMacroTask();// 2. 处理对应的所有微任务for (microTask of microTaskQueue) {handleMicroTask(microTask);}
}

在浏览器环境中,常见的宏任务和微任务有:

  • 宏任务(macro task)有setTimeoutMessageChannelpostMessagesetImmediate
  • 微任务(micro task)有MutationObseverPromise.then

了解了关于JS的事件循环后,回到Vue中,Vue内部其实也需要维护一个异步更新的任务队列,那么最好的办法就是借鉴JS原生提供的能力。

Vue在内部对异步队列尝试使用原生的Promise.thenMutationObserversetImmediate,如果执行环境不支持,则会采用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
}

首先声明了两个变量: microTimerFuncmacroTimerFunc ,它们分别对应的是 micro task 的函数和 macro task 的函数。对于 macro task 的实现,优先检测是否支持原生 setImmediate,这是一个高版本 IEEdge 才支持的特性,不支持的话再去检测是否支持原生的 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 执行 flushCallbacksflushCallbacks 的逻辑非常简单,对 callbacks 遍历,然后执行相应的回调函数。

nextTick 函数最后还有一段逻辑:

if (!cb && typeof Promise !== 'undefined') {return new Promise(resolve => {_resolve = resolve})
}

这是当 nextTick 不传 cb 参数的时候,提供一个 Promise 化的调用,比如:

nextTick().then(() => {})

_resolve 函数执行,就会跳到 then 的逻辑中。

这里有两个问题需要注意:

  1. 如何保证只在接收第一个回调函数时执行异步方法?

nextTick源码中使用了一个异步锁的概念,即接收第一个回调函数时,先关上锁,执行异步方法。此时,浏览器处于等待执行完同步代码就执行异步代码的情况。

  1. 执行 flushCallbacks 函数时为什么需要备份回调函数队列?执行的也是备份的回调函数队列?

因为,会出现这么一种情况:nextTick 的回调函数中还使用 nextTick。如果 flushCallbacks 不做特殊处理,直接循环执行回调函数,会导致里面nextTick 中的回调函数会进入回调队列。

以上就是对 nextTick 的源码分析,我们了解到数据的变化到 DOM 的重新渲染是一个异步过程,发生在下一个 tick。当我们在实际开发中,比如从服务端接口去获取数据的时候,数据做了修改,如果我们的某些方法去依赖了数据修改后的 DOM 变化,我们就必须在 nextTick 后执行。

总结

今天带着大家探索了一下Vue中的一个全局API $nextTick的基本用法和实现原理,牵扯出了很多的知识点,Vue的虚拟DOMVue更新DOM的机制、JS的事件循环,跟随Vue的源码和大家共同学习一下。

QQ: 505417246
WX: 18331092918
公众号: Code程序人生
B站账号: LuckyRay123
个人博客: http://rayblog.ltd/
欢迎关注我的各类账号, 持续更新优质前端内容

关于Vue中$nextTick的作用及实现原理(Vue进阶)相关推荐

  1. 什么是Vue生命周期?Vue生命周期的作用是什么?vue八种钩子函数

    1.vue生命周期介绍 vue生命周期是指vue对象从创建到销毁的过程.也就是vue对象从开始创建.初始化数据.编译模板.挂载Dom.渲染→更新→渲染.卸载这一系列过程. 其作用是在vue生命周期的不 ...

  2. Vue 曝光埋点插件的实现原理

    VUE 曝光埋点插件的实现原理 vue自定义 插件 说到vue插件,vue就提供了自定义插件的功能. 定义 定义一个文件,这个文件 导出一个对象,这个对象包含install方法. install方法有 ...

  3. Vue使用nextTick的原因和作用

    在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 DOM. 疑问: DOM 更新循环是指什么? 下次更新循环是什么时候? 修改数据之后使用,是加快了数据更新进 ...

  4. Vue中$nextTick的理解

    Vue中$nextTick的理解 Vue中$nextTick方法将回调延迟到下次DOM更新循环之后执行,也就是在下次DOM更新循环结束之后执行延迟回调,在修改数据之后立即使用这个方法,能够获取更新后的 ...

  5. updated beforeUpdate() Updated() 生命周期-销毁阶段 vue的nextTick@stage3---week2--day4-1

    updated && beforeUpdate() 和 Updated()钩子函数 && 生命周期-销毁阶段-外部销毁 && 生命周期-销毁阶段-内部销 ...

  6. VUE this.$nextTick()的使用场景

    [TAG - 1]: 官网解释: 将回调延迟到下次 DOM 更新循环之后执行.在修改数据之后立即使用它,然后等待 DOM 更新. 它跟全局方法 Vue.nextTick 一样,不同的是回调的 this ...

  7. vue 之 nextTick 与$nextTick

    VUE中Vue.nextTick()和this.$nextTick()怎么使用? 官方文档是这样解释的: 在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 D ...

  8. vue的$nextTick使用总结,this.$refs为undefined的解决办法,element Ui的树形控件setCheckedKeys方法无法使用

    vue的$nextTick使用总结,this.$refs为undefined的解决办法,element Ui的树形控件setCheckedKeys方法无法使用 参考文章: (1)vue的$nextTi ...

  9. mockjs vue ajax,mockjs在vue中的使用

    mockjs在vue中的使用 mockjs在vue中的使用 前后端分工协作是一个非常高效的做法,但是有时前后端分离不彻底会很痛苦.前后端应该是异步进行的,进度互不影响,但是在没有mock的时候,前端却 ...

最新文章

  1. matlab计算数字滤波器的幅频响应
  2. 如何开发auto complete 智能提示功能
  3. hdu 6386 Age of Moyu (重边判断)
  4. C语言进阶深度学习目录表
  5. Java Long类shortValue()方法与示例
  6. 【java】dubbo基础学习
  7. 最常见到的runtime exception 异常
  8. autohotkey循环
  9. catboost原理
  10. 程序员必学电脑计算机专业英语词汇 07 (142 单词)
  11. cesium实现动态立体墙效果
  12. 下列关于python运算符的使用描述正确的是_以下关于 Python 字符串的描述中,正确的是( )...
  13. java中jsp内建对象有_JSP内置对象有哪些
  14. Automa 和看小说脚本
  15. “Unable to create the directory [XXX] to use as the base directory”的解决办法
  16. Go const和iota 使用实战
  17. C++ 比赛时 无穷小、无穷大的设定
  18. Qt Mediaplayer videoplayer 例子工程 Media Player Example 应用过程中出现的问题(一)视频无法播放
  19. Spark LDA 主题抽取
  20. C语言 if.....else语句(双分支结构)

热门文章

  1. ffmpeg 从现有视频中截取一段
  2. zzuli OJ 2332:小新同学找女朋友
  3. 自学Java系列 笔记2 高级类特性1
  4. 学习笔记25马氏链模型
  5. 计算机EI检索论文,EI检索论文
  6. 山东双软认证需要什么条件
  7. 拉格朗日四平方和定理
  8. 通过代码实现EXE文件图标的替换
  9. 人工智能.黑白棋规则
  10. 达达,不想跳出京东舒适圈