$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中的回调

相关推荐
a cool fish(无名)9 分钟前
rust-模块树中引用项的路径
java·前端·rust
前端进阶者19 分钟前
天地图Marker跳一跳动画
前端
火柴就是我23 分钟前
每日见闻之Three.js 根据官方demo 理解相机位置
前端
JosieBook32 分钟前
【web应用】基于Vue3和Spring Boot的课程管理前后端数据交互过程
前端·spring boot·交互
刘大猫.39 分钟前
npm ERR! cb() never called!
前端·npm·node.js·npm install·npmm err·never called
咔咔一顿操作43 分钟前
常见问题三
前端·javascript·vue.js·前端框架
前端程序媛Ying44 分钟前
点击按钮滚动到底功能vue的v-on:scroll运用
javascript
上单带刀不带妹44 分钟前
Web Worker:解锁浏览器多线程,提升前端性能与体验
前端·js·web worke
电商API大数据接口开发Cris1 小时前
Node.js + TypeScript 开发健壮的淘宝商品 API SDK
前端·数据挖掘·api
还要啥名字1 小时前
基于elpis下 DSL有感
前端