浅析 Vue 2.6 中的 nextTick 方法

栏目: JavaScript · 发布时间: 5年前

内容简介:浅析JS 的这个

浅析 Vue 2.6 中的 nextTick 方法。

事件循环

JS 的 事件循环任务队列 其实是理解 nextTick 概念的关键。

这个 网上 其实有很多优质的文章做了详细介绍,我就简单过过了。

以下内容适用于浏览器端 JS,NodeJS 的事件循环机制并不相同。

规范 中规定 task 分为两大类: task(macrotask)microtask

通常认为是 task 的任务源:

setTimeout / setInterval
setImmediate
MessageChannel
I/O
UI rendering

通常认为是 microtask 的任务源:

Promise
process.nextTick
MutationObserver
Object.observe(已废弃)

简单概况: (这里是官方规范)

  1. 首先开始 执行 script 脚本 ,直到执行上下文栈为空时,开始清空 microtask 队列 里的任务,队列嘛,先入先出,出一个执行一个,清空完毕,走事件循环。
  2. 事件循环 :不断地去取 task 队列 的中的一个任务推入栈中执行,并在当次循环里依次清空 microtask 队列 里的任务,清空之后,可能会触发页面更新渲染(由浏览器决定)。
  3. 之后重复 事件循环 步骤。

nextTick

Vue 中数据的变化到 DOM 的更新渲染是一个异步过程。

此方法便用于在 DOM 更新循环结束之后执行延迟回调。

使用方法很简单:

// 修改数据
vm.msg = 'Hello';
// DOM 还没有更新
Vue.nextTick(function() {
  // DOM 更新了
});

// 作为一个 Promise 使用
Vue.nextTick().then(function() {
  // DOM 更新了
});

源码 去除注释,其实也只有不到一百来行,整体还是很容易理解的。

这里划成 3 个部分介绍。

模块变量

介绍 引入的模块 和 定义的变量。

// noop 空函数,可用作函数占位符
import { noop } from 'shared/util';

// Vue 内部的错误处理函数
import { handleError } from './error';

// 判断是IE/IOS/内置函数
import { isIE, isIOS, isNative } from './env';

// 使用 MicroTask 的标识符
export let isUsingMicroTask = false;

// 以数组形式存储执行的函数
const callbacks = [];

// nextTick 执行状态
let pending = false;

// 遍历函数数组执行每一项函数
function flushCallbacks() {
  pending = false;
  const copies = callbacks.slice(0);
  callbacks.length = 0;
  for (let i = 0; i < copies.length; i++) {
    copies[i]();
  }
}

异步延迟函数

接下来是核心的 异步延迟函数 。这里不同的 Vue 版本采用的策略其实并不相同。

2.6版本优先使用 microtask 作为异步延迟包装器。

2.5版本则是 macrotask 结合 microtask 。然而,在重绘之前状态改变时会有小问题(如 #6813 )。此外,在事件处理程序中使用 macrotask 会导致一些无法规避的奇怪行为(如 #7109#7153#7546#7834#8109 )。

所以 2.6 版本现在又改用 microtask 了,为什么是又呢。。因为 2.4 版本及之前也是用的 microtask 。。。

microtask在某些情况下也是会有问题的,因为 microtask 优先级比较高,事件会在顺序事件(如 #4521#6690 有变通方法)之间甚至在同一事件的冒泡过程中触发( #6566 )。

// 核心的异步延迟函数,用于异步延迟调用 flushCallbacks 函数
let timerFunc;

// timerFunc 优先使用原生 Promise
// 原本 MutationObserver 支持更广,但在 iOS >= 9.3.3 的 UIWebView 中,触摸事件处理程序中触发会产生严重错误
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve();
  timerFunc = () => {
    p.then(flushCallbacks);

    // IOS 的 UIWebView,Promise.then 回调被推入 microtask 队列但是队列可能不会如期执行。
    // 因此,添加一个空计时器“强制”执行 microtask 队列。
    if (isIOS) setTimeout(noop);
  };
  isUsingMicroTask = true;

  // 当原生 Promise 不可用时,timerFunc 使用原生 MutationObserver
  // 如 PhantomJS,iOS7,Android 4.4
  // #6466 MutationObserver 在 IE11 并不可靠,所以这里排除了 IE
} else if (
  !isIE &&
  typeof MutationObserver !== 'undefined' &&
  (isNative(MutationObserver) ||
    // PhantomJS 和 iOS 7.x
    MutationObserver.toString() === '[object MutationObserverConstructor]')
) {
  let counter = 1;
  const observer = new MutationObserver(flushCallbacks);
  const textNode = document.createTextNode(String(counter));
  observer.observe(textNode, {
    characterData: true,
  });
  timerFunc = () => {
    counter = (counter + 1) % 2;
    textNode.data = String(counter);
  };
  isUsingMicroTask = true;

  // 如果原生 setImmediate 可用,timerFunc 使用原生 setImmediate
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks);
  };
} else {
  // 最后的倔强,timerFunc 使用 setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0);
  };
}

一句话总结优先级: microtask 优先

Promise> MutationObserver > setImmediate > setTimeout

nextTick 函数

nextTick 函数。接受两个参数:

  1. cb 回调函数 :是要延迟执行的函数;
  2. ctx :指定 cb 回调函数 的 this 指向

Vue 实例方法 $nextTick 做了进一步封装,把 ctx 设置为 当前 Vue 实例

export function nextTick(cb?: Function, ctx?: Object) {
  let _resolve;

  // 调用函数,cb 回调函数会经统一处理压入 callbacks 数组
  callbacks.push(() => {
    if (cb) {
      // 给 cb 回调函数执行加上了 try-catch 错误处理
      try {
        cb.call(ctx);
      } catch (e) {
        handleError(e, ctx, 'nextTick');
      }
    } else if (_resolve) {
      _resolve(ctx);
    }
  });

  // 执行异步延迟函数 timerFunc
  if (!pending) {
    pending = true;
    timerFunc();
  }

  // 当 nextTick 没有传入函数参数的时候,返回一个 Promise 化的调用
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve;
    });
  }
}

小结

整体看过来,感觉还是比较好理解的吧~ 2.6 版本相比之前简化了一点。

小结一下,每次调用 Vue.nextTick(cb) 会做些什么:

cb 函数经处理压入 callbacks 数组 ,执行 timerFunc 函数 ,延迟调用 flushCallbacks 函数 ,遍历执行 callbacks 数组 中的所有函数。

延迟调用优先级如下:

Promise> MutationObserver > setImmediate > setTimeout

版本差异

其实 Vue 2.4、2.5、2.6 版本的 nextTick 都略不一样。

整体 2.62.4 的比较相似。(仔细瞅了瞅,基本就是一样的,2.6 timerFunc 多了个 setImmediate 判断)

2.5版本其实也差不多。。。源码写法有些不一样,整体优先级是: Promise > setImmediate > MessageChannel > setTimeout 。感兴趣可自行去末尾的参考地址进行查阅。

参考:


以上所述就是小编给大家介绍的《浅析 Vue 2.6 中的 nextTick 方法》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

触动人心

触动人心

Josh Clark / 包季真 / 电子工业出版社 / 2011-10 / 79.00元

本书是《Tapworthy: Designing Great iPhone Apps》的中文翻译版。 可能你设计网站产品或软件界面早已得心应手,可是遇到了iPhone,却感觉无从下手。 无论你是产品经理、设计师、创业者还是程序员,本书都能告诉你如何从iPhone的角度来思考应用设计。本书能帮助你理解如何设计iPhone应用,要创建一款触动人心的应用,需要如何去综合思考设计、心理、文化、......一起来看看 《触动人心》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

SHA 加密
SHA 加密

SHA 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试