$nextTick有什么作用?

$nextTick有什么作用?

一、NextTick是什么

为什么要有nexttick

二、使用场景

三、实现原理

$nextTick有什么作用?

一、NextTick是什么

官方对其的定义

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM

什么意思呢?

我们可以理解成,Vue 在更新 DOM 时是异步执行的。当数据发生变化,Vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新

举例一下

Html结构

复制代码
1<div id="app"> {{ message }} </div>

构建一个vue实例

复制代码
1const vm = new Vue({
2  el: '#app',
3  data: {
4    message: '原始值'
5  }
6})

修改message

复制代码
1this.message = '修改后的值1'
2this.message = '修改后的值2'
3this.message = '修改后的值3'

这时候想获取页面最新的DOM节点,却发现获取到的是旧值

复制代码
1console.log(vm.$el.textContent) // 原始值

这是因为message数据在发现变化的时候,vue并不会立刻去更新Dom,而是将修改数据的操作放在了一个异步操作队列中

如果我们一直修改相同数据,异步操作队列还会进行去重

等待同一事件循环中的所有数据变化完成之后,会将队列中的事件拿来进行处理,进行DOM的更新

为什么要有nexttick

举个例子

复制代码
1{{num}}
2for(let i=0; i<100000; i++){
3    num = i
4}

如果没有 nextTick 更新机制,那么 num 每次更新值都会触发视图更新(上面这段代码也就是会更新10万次视图),有了nextTick机制,只需要更新一次,所以nextTick本质是一种优化策略

二、使用场景

如果想要在修改数据后立刻得到更新后的DOM结构,可以使用Vue.nextTick()

第一个参数为:回调函数(可以获取最近的DOM结构)

第二个参数为:执行函数上下文

复制代码
1// 修改数据
2vm.message = '修改后的值'
3// DOM 还没有更新
4console.log(vm.$el.textContent) // 原始的值
5Vue.nextTick(function () {
6  // DOM 更新了
7  console.log(vm.$el.textContent) // 修改后的值
8})

组件内使用 vm.$nextTick() 实例方法只需要通过this.$nextTick(),并且回调函数中的 this 将自动绑定到当前的 Vue 实例上

复制代码
1this.message = '修改后的值'
2console.log(this.$el.textContent) // => '原始的值'
3this.$nextTick(function () {
4    console.log(this.$el.textContent) // => '修改后的值'
5})

$nextTick() 会返回一个 Promise 对象,可以是用async/await完成相同作用的事情

复制代码
1this.message = '修改后的值'
2console.log(this.$el.textContent) // => '原始的值'
3await this.$nextTick()
4console.log(this.$el.textContent) // => '修改后的值'

三、实现原理

源码位置:/src/core/util/next-tick.js

callbacks也就是异步操作队列

callbacks新增回调函数后又执行了timerFunc函数,pending是用来标识同一个时间只能执行一次

复制代码
1export function nextTick(cb?: Function, ctx?: Object) {
2  let _resolve;
3
4  // cb 回调函数会经统一处理压入 callbacks 数组
5  callbacks.push(() => {
6    if (cb) {
7      // 给 cb 回调函数执行加上了 try-catch 错误处理
8      try {
9        cb.call(ctx);
10      } catch (e) {
11        handleError(e, ctx, 'nextTick');
12      }
13    } else if (_resolve) {
14      _resolve(ctx);
15    }
16  });
17
18  // 执行异步延迟函数 timerFunc
19  if (!pending) {
20    pending = true;
21    timerFunc();
22  }
23
24  // 当 nextTick 没有传入函数参数的时候,返回一个 Promise 化的调用
25  if (!cb && typeof Promise !== 'undefined') {
26    return new Promise(resolve => {
27      _resolve = resolve;
28    });
29  }
30}

timerFunc函数定义,这里是根据当前环境支持什么方法则确定调用哪个,分别有:

复制代码
Promise.then`、`MutationObserver`、`setImmediate`、`setTimeout

通过上面任意一种方法,进行降级操作

复制代码
1export let isUsingMicroTask = false
2if (typeof Promise !== 'undefined' && isNative(Promise)) {
3  //判断1:是否原生支持Promise
4  const p = Promise.resolve()
5  timerFunc = () => {
6    p.then(flushCallbacks)
7    if (isIOS) setTimeout(noop)
8  }
9  isUsingMicroTask = true
10} else if (!isIE && typeof MutationObserver !== 'undefined' && (
11  isNative(MutationObserver) ||
12  MutationObserver.toString() === '[object MutationObserverConstructor]'
13)) {
14  //判断2:是否原生支持MutationObserver
15  let counter = 1
16  const observer = new MutationObserver(flushCallbacks)
17  const textNode = document.createTextNode(String(counter))
18  observer.observe(textNode, {
19    characterData: true
20  })
21  timerFunc = () => {
22    counter = (counter + 1) % 2
23    textNode.data = String(counter)
24  }
25  isUsingMicroTask = true
26} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
27  //判断3:是否原生支持setImmediate
28  timerFunc = () => {
29    setImmediate(flushCallbacks)
30  }
31} else {
32  //判断4:上面都不行,直接用setTimeout
33  timerFunc = () => {
34    setTimeout(flushCallbacks, 0)
35  }
36}

无论是微任务还是宏任务,都会放到flushCallbacks使用

这里将callbacks里面的函数复制一份,同时callbacks置空

依次执行callbacks里面的函数

复制代码
1function flushCallbacks () {
2  pending = false
3  const copies = callbacks.slice(0)
4  callbacks.length = 0
5  for (let i = 0; i < copies.length; i++) {
6    copies[i]()
7  }
8}

小结:

  1. 把回调函数放入callbacks等待执行

  2. 将执行函数放到微任务或者宏任务中

  3. 事件循环到了微任务或者宏任务,执行函数依次执行callbacks中的回调

相关推荐
Rkgua7 小时前
ESModule和Commonjs模块的区别
前端·javascript
江南十四行7 小时前
ReAct Agent 基本理论与项目实战(二)
前端·react.js·前端框架
用户600071819107 小时前
【翻译】React 如何乱序流式输出 UI,却仍保持最终顺序
前端
江南十四行7 小时前
AI Agent应用类型及Function Calling开发实战(三)
服务器·前端·javascript
yqcoder7 小时前
JavaScript 数据类型全景图:从基础到进阶
开发语言·javascript·ecmascript
GISer_Jing7 小时前
AI原生全栈架构理论体系:从分布式范式演进到全链路工程化理论基石
前端·人工智能·学习·ai编程
GISer_Jing7 小时前
从“切图仔”到“增长架构师”:AI时代营销前端的范式革命
前端·人工智能·ai编程
广州华水科技8 小时前
单北斗GNSS在水库变形监测中的应用与系统安装解析
前端
xingpanvip8 小时前
星盘接口开发文档:组合三限盘接口指南
android·开发语言·前端·python·php·lua
阿拉丁的梦8 小时前
blender最好的多通道吸色工具(拾取纹理颜色排除灯光)
前端·html