• 技术文章 >web前端 >js教程

    在Vuejs中通过nextTick()实现异步更新队列

    亚连亚连2018-06-14 15:11:36原创903
    本篇文章主要介绍了浅谈Vuejs中nextTick()异步更新队列源码解析,现在分享给大家,也给大家做个参考。

    vue官网关于此解释说明如下:

    vue2.0里面的深入响应式原理的异步更新队列

    官网说明如下:

    只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会一次推入到队列中。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际(已去重的)工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MutationObserver,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。

    例如,当你设置 vm.someData = ‘new value' ,该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数情况我们不需要关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。例如

    源码解析

    方法原型以及解析注释如下:

    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 !== 'undefined' && 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 !== 'undefined' && (
            isNative(MutationObserver) ||
            // PhantomJS and iOS 7.x
            MutationObserver.toString() === '[object MutationObserverConstructor]'
          )) {
          // 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 !== 'undefined') {
            return new Promise(function (resolve) {
              _resolve = resolve;
            })
          }
        }
      })();

    我在注释中解释了nextTick()函数的逻辑

    上面处理回调的三个方式的使用优先级的原因:因为Promise和MutationObserver和触发的事件在同一个事件循环里面(只不过是运行在微观队列里面),但是setTimeout的回调函数是运行在下次时间循环里面。

    优先使用Promise的原因是MutationObserver在ios9.3.3以上版本的UIWebview中运行一段时间后就停止了。
    上面代码的注释已经完全说明了代码逻辑。简单理解:将callback 推到队列里面,如果还没有执行过在下次事件循环执行触发callback函数。

    注意: 如果使用nextTick()不设置回调函数,而是使用Promise的方式设置回调函数,里面this并不是指向当前的Vue实例,而是指向window(严格模式是undefined);
    但是通过上面的分析可知:执行上下文是通过Promise.then()里的回调函数的第一个参数传递的。

    nextTick()被使用的地方

    1、他是全局Vue的一个函数,因此我们可以通过vue直接调用。

    2、Vue系统中,用于处理dom更新的操作

    Vue中有一个watcher,用于观察数据的变化,然后更新dom。前面我们就知道Vue里面不是每一次数据改变都会触发更新dom,而是将这些操作都缓存在一个队列,在一个事件循环结束之后,刷新队列,统一执行dom更新操作。

    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);
          }
        }
      }

    简单说明上面代码的逻辑,因为是watcher那里的代码,以后会分析到。这里nextTick()的作用,是在此次事件循环结尾的时候刷新watcher检查的dom更新操作。

    3、局部Vue触发$nextTick(),在dom更新后执行相应逻辑。

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

    上面是renderMinxin中的一段代码,也就是render模块初始化的代码。

    总结

    如果不了解它的代码,我们会产生理解误区。

    1、nextTick()并不会重绘当前页面,并且它也不是在页面重绘才执行,而是在此次事件循环结束后一定会执行的。

    2、此方法的触发并不是在页面更新完成才执行,第一条已经说了,但是为什么能在此方法中取到更新后的数据,那是因为dom元素的属性已经在watcher执行flush队列的时候改变了,因此是可以在此时获取的。

    证明上述观点的实例:

    h5有一个方法requestFrameAnimation(callback),此方法的回调是在页面重绘之前调用。通过实验,更新dom,nextTick()在此方法之前执行。

    上面是我整理给大家的,希望今后会对大家有帮助。

    相关文章:

    在Bootstrap框架里使用treeview如何实现动态加载数据

    关于网站生成章节目录代码示例

    详细介绍Vue数据绑定

    以上就是在Vuejs中通过nextTick()实现异步更新队列的详细内容,更多请关注php中文网其它相关文章!

    声明:本文原创发布php中文网,转载请注明出处,感谢您的尊重!如有疑问,请联系admin@php.cn处理
    专题推荐:Vue 异步更新队列
    上一篇:在three.js中如何实现3D模型展示 下一篇:mint-ui在手机端做出下拉刷新与上拉加载
    大前端线上培训班

    相关文章推荐

    • innerhtml是jquery方法么• javascript怎么设置标签的背景颜色• jquery select 不可编辑怎么办• javascript 怎么将时间转毫秒• 浅谈怎么利用node提升工作效率

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网