• 通过microtask和macrotask理解Vue.nextTick()的实现


    一丶JavaScript 运行机制详解:再谈Event Loop

       1.JavaScript 运行机制详解:

          JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

    所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

    为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

        单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

       JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。

        于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行 

    (1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

    (2)主线程之外,还存在一个"任务队列"(task queue)。

    (3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

    (4)主线程不断重复上面的第三步。

      注:只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制

       任务队列:

        "任务队列"中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入"任务队列",等待主线程读取。"任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。

        2.Event Loop
         主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)

      3.   任务队列中的microtask和macrotask

      任务队列(task queue)中的异步任务分为两种:微任务(microtask)宏任务(macrotask)

          宏任务(macrotask): 在浏览器端,其可以理解为该任务执行完后,在下一个macrotask执行开始前,浏览器可以进行页面渲染。触发macrotask任务的操作包括:

               setTimeoutsetIntervalsetImmediateI/OUI renderingmacro task事件:

          备注:宏任务一般是当前事件循环的最后一个任务,ui的渲染在这里最后执行,浏览器的ui绘制会插在每个macrotask之间,这就是为什么angularjs中settimeout会触发视图更新的                   原因。阻塞macrotask会导致ui数据不能更新

          这里注意:script(整体代码)即一开始在主执行栈中的同步代码本质上也属于macrotask,属于第一个执行的task

          微任务microtask可以理解为页面渲染前立即执行的任务,值得注意的是,UI Rendering是在micro-task之后执行。给micro-task队列添加过多回调阻塞macro-task队列的任务        执行是小事,重点是这有可能会阻塞UI Render,导致页面不能更新。浏览器也会基于性能方面的考虑,对micro-task中的任务个数进行限制。触发microtask任务的操作包括:

        Promises(浏览器实现的原生Promise)MutationObserverprocess.nextTick

          备注:事件冒泡甚至会在microtask中的任务执行之后,microtask优先级非常高

          Macrotasks、Microtasks执行机制:

          1.主线程执行完后会先到micro-task队列中读取可执行任务

          2.主线程执行micro-task任务

          3.主线程到macro-task任务队列中读取可执行任务

          4.主线程执行macro-task任务

          5....转到Step 1

    二丶vue.nextTick实现
          在 Vue.js 里是数据驱动视图变化,由于 JS 执行是单线程的,在一个 tick (task)的过程中,它可能会多次修改数据,但 Vue.js 并不会傻到每修改一次数据就去驱动一次视图变化,它会把这些数据的修改全部 push 到一个队列里,然后内部调用 一次 nextTick 去更新视图,所以数据到 DOM 视图的变化是需要在下一个 tick 才能完成。这便是我们为什么需要vue.nextTick.
         在每个 task 运行完以后,UI 都会重渲染,那么很容易想到在 microtask 中就完成数据更新(数据更新并不代表这ui更新),当前 task 结束就可以得到最新的 UI 了。反之如果新建一个 task 来做数据更新,那么渲染就会进行两次。
    vue.nextTick:
         定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。(修改数据之后Ui不会立即触发更新)

         理解:nextTick(),是将回调函数延迟在下一次dom更新数据后调用,简单的理解是:当数据更新了,在dom中渲染后,自动执行该函数.

         使用场景:

         1.1、Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中,原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted钩子函数,因为该钩子函数执行时所有的DOM挂载已完成。

         1.2、当项目中你想在改变DOM元素的数据后基于新的dom做点什么,对新DOM一系列的js操作都需要放进Vue.nextTick()的回调函数中;通俗的理解是:更改数据后当你想立即使用js操作新的视图的时候需要使用它

         1.3、在使用某个第三方插件时 ,希望在vue生成的某些dom动态发生变化时重新应用该插件,也会用到该方法,这时候就需要在 $nextTick 的回调函数中执行重新应用插件的方法。

         注:vue实现响应式并不是数据发生变化后dom立即变化,而是按照一定的策略(如上)来进行dom更新。

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>Page Title</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    
      </head>
      <body>
        <div id="app">
          <div id="example">
            {{message}}
          </div>
        </div>
        <script>
              var vm = new Vue({
      el: '#example',
      data: {
        message: '123'
      }
    }) 
    vm.message = 'new message'; // 更改数据   此时数据发生了变化,但是dom并没有更新,ui没有渲染
    console.log(vm.message);     //new message
    console.log(vm.$el.textContent);   // 123
    
    Vue.nextTick(function() {
      //nextTick相当于主线程正在进行macroTask,此时dom,ui已经更新
      console.log(vm.$el.textContent)    //DOM更新完成,ui已经渲染完毕
    })
    
    //备注:vue实现响应式并不是数据发生变化后dom立即变化,而是将每次数据的变化放入一个队列里,当这个task任务完成后
          //  才会更新DOM,渲染数据。
        </script>
      </body>
    </html>

    改变数据DOM不更新的解决方案

    2.1数组

    由于 JavaScript 的限制,Vue 不能检测以下变动的数组:

    当你利用索引直接设置一个项时,例如:this.items[indexOfItem] = newValue
    当你修改数组的长度时,例如:this.items.length = newLength

    解决方案:

    • Vue.set(this.items, indexOfItem, newValue)
    • this.items.splice(indexOfItem, 1, newValue)
    • this.$set(this.items, indexOfItem, newValue)  (this.$set 实例方法是全局方法 Vue.set 的一个别名)
    • this.items.splice(newLength)
    2.2对象

    由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除:

    复制代码
    var vm = new Vue({
      data: {
        userProfile: {
          name: 'Anika'
        }
      }
    })
    //你可以添加一个新的 age 属性到嵌套的 userProfile 对象:
      Vue.set(vm.userProfile, 'age', 27)
    //你还可以使用 vm.$set 实例方法,它只是全局 Vue.set 的别名:
      this.$set(this.userProfile, 'age', 27)
    //有时你可能需要为已有对象赋予多个新属性,比如使用 Object.assign() 或 _.extend()。在这种情况下,你应该用两个对象的属性创建一个新的对象。
      this.userProfile = Object.assign({}, this.userProfile, {
        age: 27,
        favoriteColor: 'Vue Green'
      })

    通俗来说:
    //删除对象属性的方法(前面对象名称,后面具体属性名):
    this.$delete(this.userProfile, "name");
     
  • 相关阅读:
    Error: bzip2: Compressed file ends unexpectedly; # perhaps it is corrupted?
    诡异shellbash脚本没写错运行的时候不报错也不执行
    seeksv
    常用Linux对脚本的基本操作——持续更新
    lumpy-sv
    常用linux对系统的基本操作——持续更新
    常用linux对文件的基本操作——持续更新
    css浮动与定位
    CSS知识点概要
    HTML5新手入门基础知识要点概要
  • 原文地址:https://www.cnblogs.com/8080zh/p/12627988.html
Copyright © 2020-2023  润新知