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
    感谢博主分享!
  • 相关阅读:
    Power BI 根据用户权限动态生成导航跳转目标
    Power BI Tooltips 增强功能
    Power BI refresh error “could not load file or assembly…provided impersonation level is invalid”
    SQL 错误代码 18456
    如何使用SQL Server Integration Services从多个Excel文件读取数据
    通过表格编辑器将现有表引入Power BI数据流
    Power BI 中动态增长的柱状图
    ambari2.7.3离线安装hdp3.1.0时,ambari-hdp-1.repo中baseurl无值
    ambari 安装 cannot download file mysql-connector-java from http://8080/resource/mysql-connector-java.jar
    洛谷P4180 [BJWC2010]严格次小生成树
  • 原文地址:https://www.cnblogs.com/gaosirs/p/10595326.html
Copyright © 2011-2022 走看看