关于js执行机制的理解以及Vue.nextTick的分析

228 阅读6分钟

前言

此文章借鉴 juejin.cn/post/684490… 加上自己理解写的一篇复习文章

1. 关于js

在说js运行机制之前我们首先要了解js是一门单线程的语言。

2. Event Loop (事件循环)

因为js是单线程语言就像一群人过一座独木桥一样, 一个一个人排着队过独木桥。那么问题来了,如果有急事的人排在后面,不怎么着急的人排在前面岂不是浪费了时间又增加了情绪矛盾。
语言开发者真是想到了这一点,通过事先协商好有急事的人先排着前面过桥,不怎么着急的人排在后面过桥这样不就解决问题了吗。
js 运行机制正是与这类似,分为同步任务(先过的人)和异步任务(后过的人)。在打开一个网站的时候浏览器首先执行同步任务(DOM 结构),之后加载异步任务(视频资源、图片等)。

用图表示:

文字说明:
同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。 当指定的事情完成时,Event Table会将这个函数移入Event Queue。 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。 上述过程会不断重复,也就是常说的Event Loop(事件循环)。
图片和文字说明转自:juejin.cn/post/684490…

3. setTimeout

setTimeout(() => {
   console.log('我是异步任务');
},4000)
console.log('我是同步任务');  

解析上面的代码 首先程序检测到有setTimeout 异步函数将他注册到 Event Table 里面进行执行如果即执行完毕放到Event Queue 当中。程序主线程执行代码console.log('我是同步任务') 在主线程执行完毕之后从Event Queue 读取对应的函数结果放入主线程执行。
上面这段代码输出结果是

  1. 开始输出 "我是异步任务"
  2. 过了4.*秒后输出 "我是异步任务"

在此有人会有疑惑了为什么是4.* 秒而不是4秒呢?因为HTML 5标准规定,setTimeout的最短时间间隔是4毫秒。也就是说既然主线程没有任务也最低需要4毫秒。

4. setInterval

对于setInterval而言会每隔指定的时间将注册的函数加入Event Queue,如果主线程任务耗时太久,那么同样需要等待主线程执行完毕才能执行。

// 线程休眠
 function sleep(delay) {
  var start = (new Date()).getTime();
  while ((new Date()).getTime() - start < delay) {
    continue;
  }
} 
...code
var intervalTask = setInterval(() => {
   console.log('log');
},1000)
sleep(2000);

假设上放的定时函数执行时间超过了定义的时间间隔那么定义的时间间隔就没有了意义,也就是说每次函数执行是无缝衔接的。如下代码

var intervalTask = setInterval(() => {
   sleep(1100);
   console.log('log');
},1000)

5.macro-task(宏任务) 和 micro-task(微任务)

因为柜员同时职能处理一个来办理业务的客户,这时每一个来办理业务的人就可以认为是银行柜员的一个宏任务来存在的,当柜员处理完当前客户的问题以后,选择接待下一位,广播报号,也就是下一个宏任务的开始。 所以多个宏任务合在一起就可以认为说有一个任务队列在这,里边是当前银行中所有排号的客户。 任务队列中的都是已经完成的异步操作,而不是说注册一个异步任务就会被放在这个任务队列中,就像在银行中排号,如果叫到你的时候你不在,那么你当前的号牌就作废了,柜员会选择直接跳过进行下一个客户的业务处理,等你回来以后还需要重新取号

而且一个宏任务在执行的过程中,是可以添加一些微任务的,就像在柜台办理业务,你前边的一位老大爷可能在存款,在存款这个业务办理完以后,柜员会问老大爷还有没有其他需要办理的业务,这时老大爷想了一下:“最近P2P爆雷有点儿多,是不是要选择稳一些的理财呢”,然后告诉柜员说,要办一些理财的业务,这时候柜员肯定不能告诉老大爷说:“您再上后边取个号去,重新排队”。

所以本来快轮到你来办理业务,会因为老大爷临时添加的“理财业务”而往后推。 也许老大爷在办完理财以后还想 再办一个信用卡?或者 再买点儿纪念币? 无论是什么需求,只要是柜员能够帮她办理的,都会在处理你的业务之前来做这些事情,这些都可以认为是微任务。

在当前的微任务没有执行完成时,是不会执行下一个宏任务的。

宏任务

name 浏览器 node
I/O
setTimeout
setInterval
setImmediate
requestAnimationFrame

微任务

name 浏览器 node
process.nextTick
MutationObserver
Promise.then catch finally

执行机制如图表示:

下面来分析一段代码

setTimeout(() => console.log(4))

new Promise(resolve => {
  resolve()
  console.log(1)
}).then(_ => {
  console.log(3)
})

console.log(2)

由前面的图我们得知setTimeout就是作为宏任务来存在的,而Promise.then则是具有代表性的微任务,上述代码的执行顺序就是按照序号来输出的。

所有会进入的异步都是指的事件回调中的那部分代码 也就是说new Promise在实例化的过程中所执行的代码都是同步进行的,而then中注册的回调才是异步执行的。 在同步代码执行完成后才回去检查是否有异步任务完成,并执行对应的回调,而微任务又会在宏任务之前执行。 所以就得到了上述的输出结论1、2、3、4。

6.Vue.nextTick

首先我们到知道Vue的响应式原理 cn.vuejs.org/v2/guide/re… 总结下来就是说Vue 在更新 DOM 时是异步执行的。例如,当你设置 vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。 所以说在我们更新数据之后接下来操作DOM需要依赖修改的数据必须放在nextTick的回调当中

// 在组件当中使用  this.$nextTick
Vue.nextTick(function () {
 // set dom ....
  vm.$el.textContent === 'new message' // true
})

本文未完,更新中....