Home  >  Article  >  Web Front-end  >  Implement asynchronous update queue through nextTick() in Vuejs

Implement asynchronous update queue through nextTick() in Vuejs

亚连
亚连Original
2018-06-14 15:11:361785browse

This article mainly introduces the source code analysis of nextTick() asynchronous update queue in Vuejs. Now I will share it with you and give you a reference.

vue official website explains this as follows:

In-depth responsive principle of asynchronous update queue in vue2.0

The official website explains as follows:

As long as Upon observing data changes, Vue will open a queue and buffer all data changes that occur in the same event loop. If the same watcher is triggered multiple times, it will only be pushed into the queue once. This deduplication during buffering is important to avoid unnecessary calculations and DOM operations. Then, on the next event loop "tick", Vue flushes the queue and performs the actual (deduplicated) work. Vue internally tries to use native Promise.then and MutationObserver for asynchronous queues. If the execution environment does not support it, setTimeout(fn, 0) will be used instead.

For example, when you set vm.someData = 'new value' , the component will not re-render immediately. When the queue is flushed, the component is updated on the next "tick" when the event loop queue is cleared. Most of the time we don't need to worry about this process, but if you want to do something after the DOM state is updated, it can be a bit tricky. While Vue.js generally encourages developers to think in a "data-driven" way and avoid touching the DOM directly, there are times when we really need to do that. To wait for Vue to finish updating the DOM after the data changes, you can use Vue.nextTick(callback) immediately after the data changes. This callback function will be called after the DOM update is completed. For example

Source code analysis

The method prototype and analysis comments are as follows:

var nextTick = (function () {
    var callbacks = []; // 存储需要触发的回调函数
    var pending = false; // 是否正在等待的标识(false:允许触发在下次事件循环触发callbacks中的回调, true: 已经触发过,需要等到下次事件循环)
    var timerFunc; // 设置在下次事件循环触发callbacks的 触发函数

    //处理callbacks的函数
    function nextTickHandler () {
      pending = false;// 可以触发timeFunc
      var copies = callbacks.slice(0);//复制callback
      callbacks.length = 0;//清空callback
      for (var i = 0; i < copies.length; i++) {
        copies[i]();//触发callback回调函数
      }
    }

    //如果支持Promise,使用Promise实现
    if (typeof Promise !== &#39;undefined&#39; && isNative(Promise)) {
      var p = Promise.resolve();
      var logError = function (err) { console.error(err); };
      timerFunc = function () {
        p.then(nextTickHandler).catch(logError);
        // ios的webview下,需要强制刷新队列,执行上面的回调函数
        if (isIOS) { setTimeout(noop); }
      };

      //如果Promise不支持,但是支持MutationObserver(h5新特性,异步,当dom变动是触发,注意是所有的dom都改变结束后触发)
    } else if (typeof MutationObserver !== &#39;undefined&#39; && (
        isNative(MutationObserver) ||
        // PhantomJS and iOS 7.x
        MutationObserver.toString() === &#39;[object MutationObserverConstructor]&#39;
      )) {
      // use MutationObserver where native Promise is not available,
      // e.g. PhantomJS IE11, iOS7, Android 4.4
      var counter = 1;
      var observer = new MutationObserver(nextTickHandler);
      //创建一个textnode dom节点,并让MutationObserver 监视这个节点;而 timeFunc正是改变这个dom节点的触发函数
      var textNode = document.createTextNode(String(counter));
      observer.observe(textNode, {
        characterData: true
      });
      timerFunc = function () {
        counter = (counter + 1) % 2;
        textNode.data = String(counter);
      };
    } else {// 上面两种不支持的话,就使用setTimeout

      timerFunc = function () {
        setTimeout(nextTickHandler, 0);
      };
    }
    //nextTick接受的函数, 参数1:回调函数 参数2:回调函数的执行上下文
    return function queueNextTick (cb, ctx) {
      var _resolve;//用于接受触发 promise.then中回调的函数
      //向回调数据中pushcallback
      callbacks.push(function () {
        //如果有回调函数,执行回调函数
        if (cb) { cb.call(ctx); }
        if (_resolve) { _resolve(ctx); }//触发promise的then回调
      });
      if (!pending) {//是否执行刷新callback队列
        pending = true;
        timerFunc();
      }
      //如果没有传递回调函数,并且当前浏览器支持promise,使用promise实现
      if (!cb && typeof Promise !== &#39;undefined&#39;) {
        return new Promise(function (resolve) {
          _resolve = resolve;
        })
      }
    }
  })();

I explained the logic of the nextTick() function in the comments

The reason why the above three ways of handling callbacks use priority: because Promise and MutationObserver are in the same event loop as the triggered event (but they are just running in the micro queue), but the setTimeout callback function is running under inside the time loop.

The reason for using Promise first is that MutationObserver stops after running for a period of time in UIWebview of ios9.3.3 or above.
The comments in the above code have fully explained the code logic. Simple understanding: push the callback into the queue. If it has not been executed yet, the callback function will be triggered during the next event loop execution.

Note: If you use nextTick() without setting the callback function, but use Promise to set the callback function, this does not point to the current Vue instance, but to the window (strict mode is undefined);
But from the above analysis, we can know that the execution context is passed through the first parameter of the callback function in Promise.then().

Where nextTick() is used

1. It is a function of global Vue, so we can call it directly through vue.

2. In the Vue system, operations used to process dom updates

There is a watcher in Vue, which is used to observe changes in data and then update the dom. We knew earlier that not every data change in Vue will trigger an update to the dom. Instead, these operations are cached in a queue. After an event loop ends, the queue is refreshed and the dom update operation is performed uniformly.

function queueWatcher (watcher) {
    var id = watcher.id;
    if (has[id] == null) {
      has[id] = true;
      if (!flushing) {
        queue.push(watcher);
      } else {
        // if already flushing, splice the watcher based on its id
        // if already past its id, it will be run next immediately.
        var i = queue.length - 1;
        while (i >= 0 && queue[i].id > watcher.id) {
          i--;
        }
        queue.splice(Math.max(i, index) + 1, 0, watcher);
      }
      // queue the flush
      if (!waiting) {
        waiting = true;
        nextTick(flushSchedulerQueue);
      }
    }
  }

Briefly explain the logic of the above code, because it is the code of the watcher and will be analyzed later. The function of nextTick() here is to refresh the dom update operation checked by the watcher at the end of this event loop.

3. Partial Vue triggers $nextTick() and executes the corresponding logic after the dom is updated.

Vue.prototype.$nextTick = function (fn) {
  return nextTick(fn, this)// 设置nextTick回调函数的上下文环境是当前Vue实例
};

The above is a piece of code in renderMinxin, which is the code for initializing the render module.

Summary

If we don’t understand its code, we will have misunderstandings.

1. nextTick() will not redraw the current page, and it will not be executed until the page is redrawn, but will definitely be executed after the event loop ends.

2. The triggering of this method is not executed after the page update is completed. The first item has already been mentioned, but why can the updated data be obtained in this method? That is because the attributes of the dom element have been It changed when the watcher executed the flush queue, so it can be obtained at this time.

Examples that prove the above point of view:

h5 has a method requestFrameAnimation(callback). The callback of this method is called before the page is redrawn. Through experimentation, when updating the dom, nextTick() is executed before this method.

The above is what I compiled for everyone. I hope it will be helpful to everyone in the future.

Related articles:

How to dynamically load data using treeview in the Bootstrap framework

About website generation chapter directory code example

Detailed introduction to Vue data binding

The above is the detailed content of Implement asynchronous update queue through nextTick() in Vuejs. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn