zoukankan      html  css  js  c++  java
  • Vue $nextTick 原理

    使用场景

      在进行获取数据后,需要对新视图进行下一步操作或者其他操作时,发现获取不到 DOM。

    原因:

      这里就涉及到 Vue 一个很重要的概念:异步更新队列(JS运行机制 、 事件循环)。

      Vue 在观察到数据变化时并不是直接更新 DOM,而是开启一个队列,并缓冲同一事件循环中发生的所有数据改变。

      在缓冲时会去除重复数据,从而避免不必要的计算和DOM操作。

      然后,在下一个事件循环 tick 中,Vue 刷新队列并执行实际(已去重的)工作。

      所以如果用 for 循环来动态改变数据100次,其实它只会应用最后一次改变,如果没有这种机制,DOM就要重绘100次,是一个很大的开销,损耗性能。

    一个this.$nextTick的实现

      首先,定义变量:

    var callbacks = [];   // 缓存函数的数组
    var pending = false;  // 是否正在执行
    var timerFunc;  // 保存着要执行的函数

      然后,创建  $nextTick 内实际调用的函数

    function nextTickHandler () {
      pending = false;
      //  拷贝出函数数组副本
      var copies = callbacks.slice(0);
      //  把函数数组清空
      callbacks.length = 0;
      // 依次执行函数
      for (var i = 0; i < copies.length; i++) {
        copies[i]();
      }
    }

      其次, Vue 会根据当前浏览器环境优先使用原生的 Promise.thenMutationObserver,如果都不支持,就会采用 setTimeout 代替,目的是 延迟函数到 DOM 更新后再使用

      一、Promise.then 的延迟调用

    if (typeof Promise !== 'undefined' && isNative(Promise)) {
      var p = Promise.resolve();
      var logError = function (err) { console.error(err); };
      timerFunc = function () {
        p.then(nextTickHandler).catch(logError);
        if (isIOS) { setTimeout(noop); }
      };
    }

      如果浏览器支持Promise,那么就用Promise.then的方式来延迟函数调用,Promise.then方法可以将函数延迟到当前函数调用栈最末端,也就是函数调用栈最后调用该函数。从而做到延迟。

      二、MutationObserver

    else if (typeof MutationObserver !== 'undefined' && (
      isNative(MutationObserver) ||
      MutationObserver.toString() === '[object MutationObserverConstructor]'
    )) {
    
      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);
      };
    }

      

      MutationObserver是h5新加的一个功能,其功能是监听dom节点的变动,在所有dom变动完成后,执行回调函数。

      具体有一下几点变动的监听:

        childList:子元素的变动

        attributes:属性的变动

        characterData:节点内容或节点文本的变动

        subtree:所有下属节点(包括子节点和子节点的子节点)的变动

      可以看出,以上代码是创建了一个文本节点,来改变文本节点的内容来触发的变动,因为我们在数据模型更新后,将会引起dom节点重新渲染,所以,我们加了这样一个变动监听,用一个文本节点的变动触发监听,等所有dom渲染完后,执行函数,达到我们延迟的效果。

      三、setTimeOut 延迟器

    else {
        timerFunc = function () {
          setTimeout(nextTickHandler, 0);
        };
      }

      利用setTimeout的延迟原理,setTimeout(func, 0)会将func函数延迟到下一次函数调用栈的开始,也就是当前函数执行完毕后再执行该函数,因此完成了延迟功能。

      闭包函数

    return function queueNextTick (cb, ctx) {
        var _resolve;
        callbacks.push(function () {
          if (cb) { cb.call(ctx); }
          if (_resolve) { _resolve(ctx); }
        });
        // 如果没有函数队列在执行才执行
        if (!pending) {
          pending = true;
          timerFunc();
        }
        // promise化
        if (!cb && typeof Promise !== 'undefined') {
          console.log('进来了')
          return new Promise(function (resolve) {
            _resolve = resolve;
          })
        }
      }

      

      这个return的函数就是我们实际使用的闭包函数,每一次添加函数,都会想callbacks这个函数数组入栈。然后监听当前是否正在执行,如果没有,执行函数。这个很好理解。下面一个if是promise化。

    this.$nextTick(function () {
    
    })
    // promise化
    this.$nextTick().then(function () {
    
    }.bind(this))

      以上代码中第二种写法我们不常见,直接调用$nextTick函数然后用promise格式去书写代码,不过这个then里面需要手动绑定this,vue内部没有给做处理。

      附上完整代码:

      

    var nextTick=(function () {
        //存储需要触发的回调函数
        var callbacks=[];
        //是否正在等待的标志(false:允许触发在下次事件循环触发callbacks中的回调,
        // true: 已经触发过,需要等到下次事件循环)
        var pending=false;
        //设置在下次事件循环触发callbacks的触发函数
        var timerFunc;
        //处理callbacks的函数
        function nextTickHandler() {
            // 可以触发timeFunc
            pending=false;
            //复制callback
            var copies=callbacks.slice(0);
            //清除callback
            callbacks.length=0;
            for(var i=0;i<copies.length;i++){
                //触发callback的回调函数
                copies[i]();
            }
        }
        //如果支持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) ||
            MutationObserver.toString()==='[object MutationObserverConstructor]')){
                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 {
            //上面两种都不支持,用setTimeout
            timerFunc=function () {
                setTimeout(nextTickHandler,0);
            };
        }
        //nextTick接收的函数,参数1:回调函数 参数2:回调函数的执行上下文
        return function queueNextTick(cb,ctx) {
            //用于接收触发Promise.then中回调的函数
            //向回调函数中pushcallback
            var _resolve;
            callbacks.push(function () {
                //如果有回调函数,执行回调函数
                if(cb) {cb.call(ctx);}
                //触发Promise的then回调
                if(_resolve) {_resolve(ctx);}
            });
            //是否执行刷新callback队列
            if(!pending){
                pending=true;
                timerFunc();
            }
            //如果没有传递回调函数,并且当前浏览器支持promise,使用promise实现
            if(!cb && typeof  Promise !=='undefined'){
                return new Promise(function (resolve) {
                    _resolve=resolve;
                })
            }
        }
    })
    随笔整理自 
      https://www.cnblogs.com/xujiazheng/p/6852124.html
           https://www.jianshu.com/p/a7550c0e164f
    感谢博主分享!
  • 相关阅读:
    生命周期钩子函数
    Spring Cloud Alibaba-Gateway之路由、限流、熔断、日志、鉴权(3)
    SpringBoot项目基础搭建(1)
    Spring Cloud Alibaba-Sentinel之限流、熔断(2)
    cap理论? cp ap原则的含义
    Spring Cloud Alibaba-Nacos配置、注册(1)
    SpringCloud路由Gateway(5)
    SpringCloud熔断监控Hystrix Dashboard和Turbine(4)
    SpringCloud熔断器Hystrix引用feign(3)
    SpringCloud注册中心Eureka(2)
  • 原文地址:https://www.cnblogs.com/gaosirs/p/10595326.html
Copyright © 2011-2022 走看看