最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 这里有一篇关于nextTick你所要知道的东西~

    正文概述 掘金(叽叽复饥饥)   2021-01-31   746

    开头

    我自己本身的技术栈是Vue,之前也多多少少在看了一些关于Vue的源码层面的东西。虽然进了公司以后用的是React,但是我觉得学什么技术栈并不是最重要的,最重要的是我们在学习的过程中去弄懂它的原理,我们看源码也不是为了看而去看,更重要的是去学习人家的设计思想和设计理念以及编码风格,我们每个人一开始不都是高手,在成长的路程中,我们都是通过学习别人和模仿别人得到不断的成长,就像你在小时候学习说话和走路一样,遇到困难的我们也不要去畏惧,不要听到源码就畏缩,勇敢面对就好了,这迟早也是我们学习的过程中要面对的一座大山。这篇文章其实之前已经写了一半了,然后今天趁着有空就把它给继续完善了,以后可能自己也主要转React技术栈了,关于React的一些原理性的东西以后再好好研究。也希望大家看完这篇文章能有所学习~

    事件循环机制

    在浏览器环境中,我们可以将我们的执行任务分为宏任务和微任务

    • 宏任务: 包括整体代码scriptsetTimeoutsetIntervalsetImmediate、 I/O 操作、UI 渲染 等
    • 微任务: Promise.thenMuationObserver

    特别说明的是new Promise里面的内容是同步执行的,像new Promise(resolve(console.log('1')))是同步执行的,resolve之后.then进入微任务队列,具体的内容请往下继续看。

    在浏览器环境中: 事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。大概就是先执行同步代码,然后就将宏任务放进宏任务队列,宏任务队列中有微任务就将其放进微任务队列,当宏任务队列执行完就检查微任务队列,微任务队列为空了就开始下一轮宏任务的执行,往复循环。 宏任务 -> 微任务 -> 宏任务 -> 微任务一直循环。

    我们下面看个图有助于我们理解,另外如果要看事件循环相关的题目和讲解,可以参考我这篇文章里的事件循环小节?前端基础知识大汇总(欢迎收藏) 这里有一篇关于nextTick你所要知道的东西~

    当我们理解了基本的事件循环机制之后,我们就可以开始看看Vue里面的的nextTick是怎么运作的了。

    nextTick原理

    Vue是异步执行DOM更新的,一旦观察到数据变化,Vue就会开启一个任务队列,然后把在同一个事件循环 (Event loop) 中观察到数据变化的 Watcher(Vue源码中的Wacher类是用来更新Dep类收集到的依赖的)推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOM操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。这部分的详情可以参考Vue响应式实现的源码。

    nextTick的作用是为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback),JS是单线程的,拥有事件循环机制,nextTick的实现就是利用了事件循环的宏任务和微任务。

    当我们调用nextTick的时候,传入一个回调函数,这时候会发生以下的步骤:

    • 将回调函数传入一个callbacks数组中

    • 判断采用哪种异步回调方式

      • 首先尝试使用Promise.then(微任务)
      • 尝试使用MuationObserver(微任务)回调
      • 尝试使用 setImmediate(宏任务)回调
      • 最后尝试使用setTimeout(宏任务)回调
    • 到最后执行 flushCallbacks() 方法,遍历callbacks数组,依次执行里边的每个函数

    我们再看一眼源码,源码在(Vue2.6.12版本) src -> core -> util -> next-tick.js

    //判断宏任务和微任务的变量
    //true为正在使用微任务
    export let isUsingMicroTask = false
    
    //存放回调函数的数组
    const callbacks = []
    //该变量用来设置异步锁
    let pending = false
    
    //用来遍历执行回调函数数组里的函数
    function flushCallbacks () {
      //执行回调函数时将异步锁给重置
      pending = false
      //防止出现nextTick中包含nextTick时出现问题,浅拷贝callbacks数组之后将其清空
      const copies = callbacks.slice(0)
      callbacks.length = 0
      for (let i = 0; i < copies.length; i++) {
        copies[i]()
      }
    }
    
    let timerFunc
    
    if (typeof Promise !== 'undefined' && isNative(Promise)) {
      //判断是否支持Promise
      const p = Promise.resolve()
      timerFunc = () => {
        //执行环境支持Promise则使用Promise.then(微任务)去执行回调函数
        p.then(flushCallbacks)
        if (isIOS) setTimeout(noop)
      }
      //设置为true,正在使用微任务
      isUsingMicroTask = true
    } else if (!isIE && typeof MutationObserver !== 'undefined' && (
      isNative(MutationObserver) ||
      MutationObserver.toString() === '[object MutationObserverConstructor]'
    )) {
       //当执行环境不支持Promise的时候,我们就做此处的判断
       //如果执行环境不是IE浏览器以及支持MutationObserver这个API的话就执行这里
       //比如在IOS7,Android 4.4的环境下 (IE11中的MutationObserver是不可靠的)
      let counter = 1
      const observer = new MutationObserver(flushCallbacks)
      const textNode = document.createTextNode(String(counter))
      observer.observe(textNode, {
        characterData: true
      })
      timerFunc = () => {
        counter = (counter + 1) % 2
        textNode.data = String(counter)
      }
      isUsingMicroTask = true
    } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
      // 如果Promise和MutationObserver都不支持那就使用setImmediate
      // 该方法优先级依然比setTimeout高,setImmediate属于宏任务
      timerFunc = () => {
        setImmediate(flushCallbacks)
      }
    } else {
      //如果上面三种方法都不支持才使用setTimeout,setTimeout也属于宏任务
      timerFunc = () => {
        setTimeout(flushCallbacks, 0)
      }
    }
    
    //传入一个回调函数和一个上下文对象
    export function nextTick (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()
      }
        
      //当我们不传回调函数的时候,提供一个Promise化的调用
      //相当于可以nextTick().then(() => {})
      if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
          _resolve = resolve
        })
      }
    }
    

    我们再来看一个在Vue中的使用例子:

    Vue生命周期的created钩子函数进行DOM操作的话我们就需要使用到nextTick了。我们将DOM的操作放在Vue.nextTick()的回调函数中,原因是在created钩子函数执行的时候DOM 其实并未进行挂载和渲染,此时就是无法操作DOM的,我们将操作DOM的代码中放到nextTick中,等待下一轮事件循环开始,DOM就已经进行挂载好了,而与这个操作对应的就是mounted钩子函数,因为在mounted执行的时候所有的DOM挂载已完成。

      created(){
        this.$nextTick(() => {  
            //不使用this.$nextTick()方法操作DOM会报错
            this.$refs.test.innerHTML="created中操作了DOM"
        });
      },
    

    另外,当我们修改了data里的数据时,并不能通过操作DOM去获取到里面的值,此时我们也需要用到nextTick

    <template>
      <div class="test">
        <p id="msg">{{msg}}</p>
      </div>
    </template>
     
    <script>
    export default {
      name: 'Test',
      data () {
        return {
          msg:"你好,Vue~",
        }
      },
      methods: {
        changeMsg() {
          this.msg = "你好,王大锤!"  //vue数据改变,改变了DOM里的innerText
          let msgEle = document.getElementById('msg').innerText  //后续js对dom的操作
          console.log(msgEle)  // 输出可以看到data里的数据修改后DOM并没有立即更新,后续的DOM不是最新的
        },
      }
    }
    </script>
    

    我们使用nextTick对上面的代码进行一下改善就能看到正确的操作结果啦

      methods: {
        changeMsg() {
          this.msg = "你好,王大锤!"  //vue数据改变,改变了DOM里的innerText
          this.$nextTick(() => {
             let msgEle = document.getElementById('msg').innerText  
             console.log(msgEle)  // 输出可以看到data里的数据修改后DOM已经更新完成
          })
        },
      }
    

    总结

    文章就暂时先写到这了,其实很多时候我们学习一个东西,首先还是得学会如何去使用,等到用到一定程度的时候,就可以去学习它的设计原理了,因为我们学习一个东西不能总是浮于表层,要发展成一个T型人才,就得在某个领域里去纵向发展,然后其它领域里横向扩展,这样我们才能永远保持学习的热枕,对这个世界保持一种探索的欲望,永远不要满足于现状。


    起源地下载网 » 这里有一篇关于nextTick你所要知道的东西~

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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