最近在看vue源码,发现vue中的nextTick异步更新dom操作是先判断是否支持Promise,如果不支持就判断是否支持MutationObserval,如果也不支持的话,最后才是setTimeout。

/**
 * Defer a task to execute it asynchronously.
 */
var nextTick = (function () {
  var callbacks = [];
  var pending = false;
  var timerFunc;

  function nextTickHandler () {
    pending = false;
    var copies = callbacks.slice(0);
    callbacks.length = 0;
    for (var i = 0; i < copies.length; i++) {
      copies[i]();
    }
  }

  // the nextTick behavior leverages the microtask queue, which can be accessed
  // via either native Promise.then or MutationObserver.
  // MutationObserver has wider support, however it is seriously bugged in
  // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
  // completely stops working after triggering a few times... so, if native
  // Promise is available, we will use it:
  /* istanbul ignore if */
  if (typeof Promise !== 'undefined' && isNative(Promise)) {
    var p = Promise.resolve();
    var logError = function (err) { console.error(err); };
    timerFunc = function () {
      p.then(nextTickHandler).catch(logError);
      // in problematic UIWebViews, Promise.then doesn't completely break, but
      // it can get stuck in a weird state where callbacks are pushed into the
      // microtask queue but the queue isn't being flushed, until the browser
      // needs to do some other work, e.g. handle a timer. Therefore we can
      // "force" the microtask queue to be flushed by adding an empty timer.
      if (isIOS) { setTimeout(noop); }
    };
  } else if (!isIE && typeof MutationObserver !== 'undefined' && (
    isNative(MutationObserver) ||
    // PhantomJS and iOS 7.x
    MutationObserver.toString() === '[object MutationObserverConstructor]'
  )) {
    // use MutationObserver where native Promise is not available,
    // e.g. PhantomJS, iOS7, Android 4.4
    var counter = 1;
    var observer = new MutationObserver(nextTickHandler);
    var textNode = document.createTextNode(String(counter));
    observer.observe(textNode, {
      characterData: true
    });
    timerFunc = function () {
      counter = (counter + 1) % 2;
      textNode.data = String(counter);
    };
  } else {
    // fallback to setTimeout
    /* istanbul ignore next */
    timerFunc = function () {
      setTimeout(nextTickHandler, 0);
    };
  }

    当时觉得很奇怪,为何要这样做区分,为何要优先使用Promise,而不是优先使用setTimeout。后来才知道原来牵扯到task和micro task。

   我们先看如下一个demo,看输出结果是什么?

console.log(1);
	
setTimeout(()=>{
	console.log(2)
},100)
	
Promise.resolve().then(()=>{
	console.log(3);
})
	
console.log(4);

    结果是1 4 3 2。可能很多人会奇怪为何不是1 4 2 3!

    其实这里Promise是一个micro task,我们的主线程是一个task。micro task会在task后面执行,然后才会接着执行下一个task。而setTimeout的返回函数是一个新的task,所以这里Promise的执行会先于新task执行。根据HTML标准,一个task执行完后,UI会重渲染,所以micro task更新完数据后再渲染dom的操作要比setTimout的性能要好。如果使用setTimeout的话,会有两次ui重渲染。task和micro task的详情可以参考https://www.cnblogs.com/dong-xu/p/7000139.html

Logo

前往低代码交流专区

更多推荐