最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 简单的理解 $nextTick 原理。

    正文概述 掘金(ZHOUYUANN)   2021-03-02   695

    在使用 nextTick 的时候发现很神奇,只要是操作完 dom 更新之后就调用这个函数,然后就可以得到反馈,为了知道其中原理,所以就打开了源码仔细得看了一遍。

    在说 nextTick 之前我们先将一下节流这个东西,为什么将这个呢,看官们继续往下看。节流函数的文章呢大家也都看烂了,随便就可以搜的到。简单意思就是,在规定时间内触发,不做操作,等时间过去之后在执行。废话不讲那么多,直接看一下简化的代码:

    var throttle = function(fn, delay) {
      var time = null
      return function() {
        var self = this
        if(!time) {
          time = setTimeout(function(){
            fn.apply(self)
            time = null
          }, delay)
        }
      }
    }
    

    以上代码就是节流的简化版,创建一个空的 time 返回一个匿名函数,如果 time 有值,说明还在在规定的时间内,如果没有值,说明是新建的 time ,当 setTimeout 时间运行完成了之后,在重置一下 time 为空。

    说了这么多节流的实现,我们在回过头来再看一下 nextTick 是怎么实现。

    export const nextTick = (function () {
      const callbacks = []
      let pending = false
      let timerFunc
    
      function nextTickHandler () {
        pending = false
        const copies = callbacks.slice(0)
        callbacks.length = 0
        for (let 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 = err => { console.error(err) }
        timerFunc = () => {
          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 = () => {
          counter = (counter + 1) % 2
          textNode.data = String(counter)
        }
      } else {
        // fallback to setTimeout
        /* istanbul ignore next */
        timerFunc = () => {
          setTimeout(nextTickHandler, 0)
        }
      }
    
      return function queueNextTick (cb?: Function, ctx?: Object) {
        let _resolve
        callbacks.push(() => {
          if (cb) {
            try {
              cb.call(ctx)
            } catch (e) {
              handleError(e, ctx, 'nextTick')
            }
          } else if (_resolve) {
            _resolve(ctx)
          }
        })
        if (!pending) {
          pending = true
          timerFunc()
        }
        if (!cb && typeof Promise !== 'undefined') {
          return new Promise((resolve, reject) => {
            _resolve = resolve
          })
        }
      }
    })()
    

    写了这么多,其实没必要全都看一遍,好几个判断都是在处理最佳的方案,比如说使用 promise 如果浏览器支持就使用 new Promise ,如果是 IE 场景下就使用 MutationObserver 支持,我们也来把代码精简一下,去掉那些繁琐的,看不懂的,留下了我们熟悉的 setTimeout 函数,其中传的时间为 0,模拟一个异步的场景(实际上是为了让其进入异步队列中)。简化之后的代码为:

    var nextTick = (function () {
      var callbacks = [];
      var pending = false;
    
      return function(cb, ctx) {
        // 把要执行的函数放入 callback 数组中,里面的是函数
        callbacks.push(function () {
          // 绑定 this
          if (cb) { cb.call(ctx); }
        });
        // 如果是 pending 的状态
        if (!pending) {
          pending = true;
          // 当所有主线程执行完成之后,再执行
          setTimeout(function () {
            pending = false;
            // 把类数组变成数组
            var copies = callbacks.slice(0);
            // 清空数组
            callbacks.length = 0;
            for (var i = 0; i < copies.length; i++) {
              // 执行数组里面函数,进而更新 update
              copies[i]();
            }
          }, 0);
        }
      }
    })()
    

    以上代码是不是有点似曾相识的感觉,没错有点节流的感觉,如果同一个watcher被多次触发,则只执行最后一次,从而避免对DOM的重复操作,把更改后的数据,统一除了处理,来理一遍。

    使用了setTimeout(fn, 0),不考虑 promiseMutationObserver 使用, 实现原理是把 watch 的数据都放到模拟的缓存队列中 callbacks,等到 watch 所有数据改变完成时,然后再执行 setTimeout 方法,我们都知道 event loop,如果主线程 执行栈 “execution context stack” 遇到堵塞的时候就排队,但是如果执行的是异步操作,那么就主线程就不需要等待直接执行下面的代码,在异步中也有 任务队列 “task queue”。

    一旦 "执行栈" 中的所有同步任务执行完毕,系统就会读取 "任务队列" ,看看里面有哪些事件

    那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行, 主线程不断重复上面那步。

    是不是有点明白了?

    参考文章: segmentfault.com/a/119000001…


    起源地下载网 » 简单的理解 $nextTick 原理。

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元