浅析 Vue 2.6 中的 nextTick 方法

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

内容简介:浅析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 方法》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Programming Ruby

Programming Ruby

Dave Thomas、Chad Fowler、Andy Hunt / Pragmatic Bookshelf / 2004-10-8 / USD 44.95

Ruby is an increasingly popular, fully object-oriented dynamic programming language, hailed by many practitioners as the finest and most useful language available today. When Ruby first burst onto the......一起来看看 《Programming Ruby》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具