Vue.nextTick核心原理

news2024/11/25 21:22:59

相信大家在写vue项目的时候,一定会发现一个神奇的api,Vue.nextTick。为什么说它神奇呢,那是因为在你做某些操作不生效时,将操作写在Vue.nextTick内,就神奇的生效了。那这是什么原因呢?

让我们一起来研究一下。

简述

  • vue 实现响应式并不是数据发生变化后 DOM 立即变化,而是按照一定策略异步执行 DOM 更新的* vue 在修改数据后,视图不会立刻进行更新,而是要等同一事件循环机制内所有数据变化完成后,再统一进行DOM更新* nextTick 可以让我们在下次 DOM 更新循环结束之后执行延迟回调,用于获得更新后的 DOM。事件循环机制
    ======

在讨论Vue.nextTick之前,需要先搞清楚事件循环机制,算是实现的基石了,那我们来看一下。

在浏览器环境中,我们可以将我们的执行任务分为宏任务和微任务,

  • 宏任务: 包括整体代码scriptsetTimeoutsetIntervalsetImmediate、 I/O 操作、UI 渲染
  • 微任务: Promise.thenMuationObserver

事件循环的顺序,决定js代码的执行顺序。事件循环如下:

用代码解释,浏览器中事件循环的顺序同如下代码:

for (macroTask of macroTaskQueue) { // 1. 执行一个宏任务handleMacroTask();// 2. 执行所有的微任务for (microTask of microTaskQueue) { handleMicroTask(microTask);}
 } 

vue数据驱动视图的处理(异步变化DOM)

<template><div><div>{{count}}</div><div @click="handleClick">click</div></div>
</template>
export default {data () {return {number: 0};},methods: {handleClick () {for(let i = 0; i < 10000; i++) {this.count++;}}}
} 

分析上述代码:

  • 当点击按钮时,count会被循环改变10000次。那么每次count+1,都会触发count的setter方法,然后修改真实DOM。按此逻辑,这整个过程,DOM会被更新10000次,我们都知道DOM的操作是非常昂贵的,而且这样的操作完全没有必要。所以vue内部在派发更新时做了优化
  • 也就是,并不会每次数据改变都触发 watcher 的回调,而是把这些 watcher 先添加到一个队列queueWatcher里,然后在 nextTick 后执行 flushSchedulerQueue处理
  • 当 count 增加 10000 次时,vue内部会先将对应的 Watcher 对象给 push 进一个队列 queue 中去,等下一个 tick 的时候再去执行。并不需要在下一个 tick 的时候执行 10000 个同样的 Watcher 对象去修改界面,而是只需要执行一个 Watcher 对象,使其将界面上的 0 变成 10000 即可

Vue.nextTick原理

由上一节我们知道,Vue中 数据变化 => DOM变化 是异步过程,一旦观察到数据变化,Vue就会开启一个任务队列,然后把在同一个事件循环 (Event loop) 中观察到数据变化的 Watcher(Vue源码中的Wacher类是用来更新Dep类收集到的依赖的)推送进这个队列。

如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOM操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。

nextTick的作用是为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback),JS是单线程的,拥有事件循环机制,nextTick的实现就是利用了事件循环的宏任务和微任务。

vue中next-tick.js的源码如下

import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'

export let isUsingMicroTask = false

// 首先定义一个 callbacks 数组用来存储 nextTick,在下一个 tick 处理这些回调函数之前,
// 所有的 cb 都会被存在这个 callbacks 数组中
const callbacks = []
// pending 是一个标记位,代表一个等待的状态
let pending = false

// 最后执行 flushCallbacks() 方法,遍历callbacks数组,依次执行里边的每个函数
function flushCallbacks () {pending = falseconst copies = callbacks.slice(0)callbacks.length = 0for (let i = 0; i < copies.length; i++) {copies[i]()}
}

let timerFunc

/*判断采用哪种异步回调方式由于微任务优先级高,首先尝试微任务模拟1.首先尝试使用Promise.then(微任务)2.尝试使用MuationObserver(微任务)回调3.尝试使用 setImmediate(宏任务)回调4.最后尝试使用setTimeout(宏任务)回调*/
if (typeof Promise !== 'undefined' && isNative(Promise)) {const p = Promise.resolve()timerFunc = () => {p.then(flushCallbacks)if (isIOS) setTimeout(noop)}isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) ||// PhantomJS and iOS 7.xMutationObserver.toString() === '[object MutationObserverConstructor]'
)) {let counter = 1const observer = new MutationObserver(flushCallbacks)const textNode = document.createTextNode(String(counter))observer.observe(textNode, {characterData: true})timerFunc = () => {counter = (counter + 1) % 2textNode.data = String(counter)}isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {timerFunc = () => {setImmediate(flushCallbacks)}
} else {timerFunc = () => {setTimeout(flushCallbacks, 0)}
}

export function nextTick (cb?: Function, ctx?: Object) {let _resolvecallbacks.push(() => {if (cb) {try {cb.call(ctx)} catch (e) {handleError(e, ctx, 'nextTick')}} else if (_resolve) {_resolve(ctx)}})if (!pending) {pending = truetimerFunc()}// $flow-disable-lineif (!cb && typeof Promise !== 'undefined') {return new Promise(resolve => {_resolve = resolve})}
} 

参考vue实战视频讲解:进入学习

目前浏览器平台并没有实现 nextTick 方法,所以 Vue.js 源码中分别用 Promise、setTimeout、setImmediate 等方式在 microtask(或是task)中创建一个事件,目的是在当前调用栈执行完毕以后(不一定立即)才会去执行这个事件。

nextTick的调用方式

1.回调函数方式:Vue.nextTick(callback)
2.Promise方式:Vue.nextTick().then(callback)
3.实例方式:vm.$nextTick(callback)

Vue.nextTick的应用

created生命周期中操作DOM

created钩子函数执行的时候DOM 其实并未进行挂载和渲染,此时就是无法操作DOM的,我们将操作DOM的代码中放到nextTick中,等待下一轮事件循环开始,DOM就已经进行挂载好了,而与这个操作对应的就是mounted钩子函数,因为在mounted执行的时候所有的DOM挂载已完成。

created(){vm.$nextTick(() => {//不使用this.$nextTick()方法操作DOM会报错this.$refs.test.innerHTML="created中操作了DOM"});
} 

修改数据,获取DOM值

当我们修改了data里的数据时,并不能立刻通过操作DOM去获取到里面的值

<template><div class="test"><p ref='msg' id="msg">{{msg}}</p></div>
</template>

<script> export default {name: 'Test',data () {return {msg:"hello world",}},methods: {changeMsg() {this.msg = "hello Vue"// vue数据改变,改变了DOM里的innerTextlet msgEle = this.$refs.msg.innerText//后续js对dom的操作console.log(msgEle)// hello world// 输出可以看到data里的数据修改后DOM并没有立即更新,后续的DOM不是最新的this.$nextTick(() => {console.log(this.$refs.msg.innerText) // hello Vue})this.$nextTick().then(() => {console.log(this.$refs.msg.innerText) // hello Vue})},changeMsg2() {this.$nextTick(() => {console.log(this.$refs.msg.innerText) // 1.hello world })this.msg = "hello Vue" // 2.console.log(this.$refs.msg.innerText) // hello worldthis.$nextTick().then(() => {console.log(this.$refs.msg.innerText) // hello Vue})// nextTick中先添加的先执行,执行1后,才会执行2(Vue操作Dom的异步)}}} </script> 

v-show/v-if由隐藏变为显示

点击按钮显示原本以 v-show=false或v-if 隐藏起来的输入框,并获取焦点或者获得宽高等的场景

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/167967.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手把手教你写Dockerfile以及测试

Dockerfile是什么&#xff1f; dockerfile就是用来构建docker镜像的构建文件,命令参数脚本。 如何使用Dockerfile&#xff1f; 1、编写一个Dockerfile文件2、docker build构建成 基础使用&#xff08;此处罗列一些我们经常用到的&#xff09; # 指定依赖镜像版本&#xff…

【附代码】十大主流聚类算法

准备工作安装必要的库pip install scikit-learn准备数据集使用 make _ classification ()函数创建一个测试二分类数据集。数据集将有1000个示例&#xff0c;每个类有两个输入要素和一个群集。这些群集在两个维度上是可见的&#xff0c;因此我们可以用散点图绘制数据&#xff0c…

第18章_JDBC

一、JDBC概述JDBC概述什么是JDBCJDBC&#xff08;Java DataBase Connectivity, Java数据库连接&#xff09; ,是一种用于执行SQL语句的Java API&#xff0c;为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成有了JDBC&#xff0c;程序员只需用JDBC API写一个…

夜深忽梦少年事,7年又一年,来看看95年那个小伙现在怎么样了

2022年已到尾声&#xff0c;疫情也结束了&#xff0c;这三年太不容易了&#xff0c;今年也是一样在疫情的艰难的度过&#xff0c;就是做了两件事&#xff0c;防疫和上班&#xff0c;没什么可写的。但是在一个深夜晚上&#xff0c;想了很多以前的事&#xff0c;想想还是写一点东…

亚马逊云科技Amazon DeepRacer互联网行业全国冠军诞生

1月11日&#xff0c;首届亚马逊云科技Amazon DeepRacer自动驾驶赛车互联网行业全国总决赛圆满结束&#xff0c;从全国各地选拔出的9支冠军队伍齐聚滨海三亚&#xff0c;向总决赛的桂冠发起了冲击。 本次比赛沿袭了Amazon DeepRacer League全球赛事标准&#xff0c;使用了全新的…

Vue.js的this如何取到data和method里的属性?

本篇文章介绍的是Vue.js如何取到data和methods里的属性&#xff1f; 准备工作 克隆源码到本地 git clone https://github.com/vuejs/vue.git 下载完毕后&#xff0c;用vscode打开&#xff0c;目光移动到package.json的scripts属性&#xff0c;我们看到有dev和build&#xff0…

Golang -- openwechat发送消息、自动回复

开篇 马上就要到农历新年了&#xff0c;不妨写一段代码准时为好友们送上祝福。 该 Demo 使用开源项目 openwechat &#xff0c;实现获取好友列表、为好友发送消息、图片或文件&#xff0c;接收来自好友或群组的消息并设置自动回复等功能。 openwechat Github地址 openwechat 文…

CSS设置元素内边距(padding)、外边距(margin)

设置元素内边距padding 所有的 HTML 元素基本都是以矩形为基础。 每个 HTML 元素周围的矩形空间由三个重要的属性来控制&#xff1a; padding&#xff08;内边距&#xff09; margin&#xff08;外边距&#xff09; border&#xff08;边框&#xff09; padding控制着元素内容…

产品经理需要懂的专业术语有哪些?

不同的行业都有着不同的专业术语&#xff0c;掌握专业术语不仅是个人专业能力的体现&#xff0c;还可以进一步促进工作中的交流&#xff0c;提高工作效率。 1、工作类 BRD&#xff1a;商业文档&#xff0c;包含了商业几乎&#xff0c;产品背景&#xff0c;可行性说明&#xff…

Redis底层数据结构简介

目录 1、Redis存储结构 2、数据结构 2.1、简单动态字符串(SDS) 2.2.1、SDS数据结构 2.2.2、编码 2.2.3、SDS与C字符串对比 2.2、链表(Linkedlist) 2.2.1、链表数据结构(双向链表) 2.2.2、特性 2.3、跳表(Skiplist) 2.3.1、数据结构 2.3.2、特点 2.3.3、增删查操作…

宝元机床联网

一、设备信息确认 宝元数控在台湾也是做的比较早的数控系统品牌&#xff0c;13年被研华并购。 1、确认型号 宝元的数控面板关机情况下是没办法判断型号的&#xff0c;要在开机的一瞬间确认。 此系统为&#xff1a;M520 注&#xff1a;目前接触宝元系统基本上都含网口。 2、…

maven依赖设置

之前说过了可以通过依赖的方式将一个大程序分为多个小的模块&#xff0c;模块之间可以利用依赖链接在一起。 但是如果有多个依赖的情况下会怎么样呢&#xff1f; A依赖于B、C&#xff0c;而B、C又有各自的依赖&#xff0c;那么A是否依赖于B、C的依赖呢&#xff1f; 答案是是的…

OpenResty中Lua变量的使用

全局变量 在 OpenResty 里面&#xff0c;只有在 init_by_lua* 和 init_worker_by_lua* 阶段才能定义真正的全局变量。 这是因为其他阶段里面&#xff0c;OpenResty 会设置一个隔离的全局变量表&#xff0c;以免在处理过程污染了其他请求。 即使在上述两个可以定义全局变量的阶…

全息(CSDN_0009_20220919)

文章编号&#xff1a;CSDN_0009_20220919 目录 全息的广义概念 发展历程 全息摄影 全息投影 全息影像 全息应用 全息投影 全息的广义概念 反映物体在空间存在时的整个情况的全部信息。 特指一种技术&#xff0c;可以让从物体发射的衍射光能够被重现&#xff0c;其位置和…

uniapp获取微信openid - 微信提现 - 登录授权 - AndroidStudio离线打包微信登陆

效果图 主要步骤 (详细步骤有配图) 登录微信开放平台,获取AppID + AppSecrethttps://open.weixin.qq.com/

mysql:浅显易懂——存储引擎

mysql&#xff1a;浅显易懂——存储引擎 最近学到了存储引擎(尚硅谷的康老师视屏)。 首先存储引擎并不是什么高大上的东西&#xff0c;可以直接理解为&#xff0c;表类型 所以存储引擎是针对表的描述。 那么如何学习&#xff1f;——就学mysqll中不同的表类型。和不同类型的…

【初阶数据结构】——二叉树OJ练习

文章目录前言1. 单值二叉树思路分析思路1. 遍历对比思路2. 递归代码实现2. 判断两棵二叉树是否相同思路分析代码实现3. 另一棵树的子树思路分析代码实现4. 返回二叉树结点的前序遍历数组思路分析代码实现5. 对称二叉树思路分析代码展示前言 上一篇文章我们刚刚学完二叉树初阶的…

使用nginx的rtmp模块搭建RTMP和HLS流媒体服务器

使用nginx的rtmp模块搭建RTMP和HLS流媒体服务器 文章目录使用nginx的rtmp模块搭建RTMP和HLS流媒体服务器环境搭建参数配置验证结果前面文章中已经介绍了《使用nginx搭建rtmp流媒体服务器》和《使用nginx搭建HLS服务器》&#xff0c;其实nginx的RTMP模块本身就支持接收RTMP推流、…

【jQuery】实现文件上传和loading效果

一、 jQuery实现文件上传1. 定义UI结构<!-- 导入 jQuery --><script src"./lib/jquery.js"></script><!-- 文件选择框 --><input type"file" id"file1" /><!-- 上传文件按钮 --><button id"btnUplo…

我们是存算一体化

从最初的计算和存储分离,随着技术的发展,存算一体化越来越被大家重视,成为了下一个发展浪潮。其实对于海量数据场景来说,我们更认为数据应该是存算协同的关系。存算一体化才是最高效的技术之一,但是目前真正的存算一体,或者说革命性地突破冯•诺伊曼架构的存算一体还未实…