欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

js的事件循环机制 even loops[转载]

程序员文章站 2022-07-12 21:09:15
...

文章参考

  1. vue方法nextTick源码分析
  2. 微宏任务执行顺序比较,promise.then,process.nextTick, setTimeout 以及 setImmediate的执行顺序

eventLoop 介绍

1. js的单线程

我们知道JavaScript的一大特点就是单线程,而这个线程中拥有唯一的一个事件循环

作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题

2. 什么是事件循环呢?

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

2.主线程之外,如果有异步任务,还存在一个“任务队列”,只要异步任务有了运行结果,就在“任务队列”之中放置一个事件

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

个人理解:异步任务执行完又会去执行主任务,主任务执行完了再来执行'任务队列'的异步任务

4.主线程不断重复第3步

假定JavaScript同时有两个线程(事件)

一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以js的运行环境决定js本质就只是单线程

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务

那要是前面的代码不走了,后面就不走了。那的等到猴年马月啊。所以不行,

所以js出了个同步和异步的概念

3.同步任务(代码),异步任务(代码)

我们平时说的最多的就是同步代码,异步代码。同步代码先执行。异步代码会延后执行;

不管什么都是借助函数调用栈来执行的。都是调用函数做事的。

如何区分同步代码和异步代码呢?

同步代码

如果在函数返回的时候,调用者就能够得到预期结果(即拿到了预期的返回值或者看到了预期的效果),那么这个函数就是同步的

异步代码
如果在函数返回的时候,调用者还不能够得到预期结果,而是需要在将来通过一定的手段得到或等待一段时间,那么这个函数就是异步的

但是异步执行的代码都不是马上执行的。怎么排一个先后顺序呢?

一般而言,异步任务有以下三种类型

  1. 点击事件,如click 点击的时候,代表任务完成,放到主线程之外的任务队列里排队

  2. 资源数据加载,如load等,当响应到全部数据的时候,任务完成,去任务队列排队去

  3. 定时器,包括setInterval、setTimeout等,当时间到了的时候。去任务队列排队

这样,当主线程的同步代码完成以后,js开始从任务队列里找异步。谁在第一个就第一个出去。进入到执行栈,执行完。直到完成一个事件。继续回到主线程,执行。找异步,。。。。。在完成一个事件。。。。事件循环。。。。

主线程是栈 先进后出,任务队列是队列 先进先出

但是es6 发展起来了,出了个 promise的这个时候,异步代码就有意思了。当主线程执行完以后,会去任务队列里找异步任务。但是有些异步任务。执行完以后,并不想马上被主线程执行并清空。反而想马上执行另外的小事情。例如.then().then()。。。。这个时候异步任务就复杂了。主线程就不知道先拿你的异步小事情去做,还是拿你的异步大事情去做。。。。

所以这个时候分为了 宏任务 微任务 的概念

4.宏任务(代码),微任务(代码)

同步代码直接执行。异步代码分为宏任务 和 微任务

不同类型的异步任务的会进入不同的任务队列中:

  • 宏任务会加入宏任务队列,
  • 微任务会加入微任务队列。

在执行栈中的同步任务执行完成后,主线程会先查看任务队列中的微任务,如果没有,则去宏任务队列中取出最前面的一个事件加入执行栈中执行;如果有,则将所有在微任务队列中的事件依次加入执行栈中执行,直到所有微任务事件执行完成后,再去宏任务中取出最前面的一个事件加入执行栈,如此循环往复。

宏任务:script,setTimeout,setInterval,setImmediate

微任务:Promise,process.nextTick ,MutationObserver

看下面的一套代码。

console.log('start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('end');

执行结果是

script start
script end’
promise1
promise2
setTimeout

复制代码走一遍事件循环
每个宏任务都是一个事件

第一遍循环--------。

  1. 执行栈里面有好多的代码段,从头开始执行

  2. 同步代码输出来 start 输出

  3. 宏任务setTimeout放在宏任务队列里挂起来

  4. 微任务代码Promise后面的两个then promise1 promise2 放在微任务队列

  5. 同步代码 end 输出

  6. 根据定义,主线程同步执行完继续找微任务。执行。输出 promise1 promise2

  7. 微任务队列空了,开始第二个宏任务

第二遍循环--------。

  1. 执行栈该执行的执行了。空了

  2. 微任务空了,执行完了

  3. 去宏任务找,艾玛。发现一个。setTimeout

  4. 输出 setTimeout

再来一个例子

console.log('1')           

// 这是一个宏任务
setTimeout(function () {   
  console.log('2')                   
});

new Promise(function (resolve) {
  // 这里是同步任务
  console.log('3');         
  resolve();                          
  // then是一个微任务
}).then(function () {      
  console.log('4')          
  setTimeout(function () {
    console.log('5')
  });
});

结果 1 3 4 2 5

第一遍事件循环

  1. 执行栈里面的方法开始执行

  2. 同步任务 输出1

  3. 遇到一个setTimeout 不执行,放在宏任务队列挂起来 标记setTimeout1

  4. 遇到promise构造函数里的是同步 输出3 then里面的是微任务。挂载微任务队列(里面有宏任务嵌套。不用管,直接放微任务)

  5. 同步执行完了。找微任务。 输出4。艾玛里面有个宏任务。再放进宏任务。标记setTimeout2

  6. 同步执行完了。微任务也完了。改下一个宏任务了

第二遍事件循环

  1. 执行栈里面的方法开始执行

  2. 没有同步代码

  3. 微任务队列也没有

  4. 找宏任务setTimeout1。输出2

  5. 走下一个宏任务了

第三遍事件循环

  1. 执行栈里面的方法开始执行

  2. 没有同步代码

  3. 微任务队列也没有

  4. 找宏任务setTimeout2。输出5

  5. 走下一个宏任务了.。。也没有了 执行了三次事件循环

我们只需记住:

  1. 当当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件

  2. 同一次事件循环中,微任务永远在宏任务之前执行。

  3. 每个宏任务就开始一次事件循环

微宏任务执行顺序比较?

promise.then,
process.nextTick,
setTimeout
setImmediate的执行顺序?

setImmediate(function(){
  console.log(1);
},0);
setTimeout(function(){
  console.log(2);
},0);
new Promise(function(resolve){
  console.log(3);
  resolve();
  console.log(4);
}).then(function(){
  console.log(5);
});
console.log(6);
process.nextTick(function(){
  console.log(7);
});
console.log(8);

// 答案是:3 4 6 8 7 5 2 1

第一步:执行完主任务

  1. 创建setImmediate 宏任务

  2. 创建setTimeout 宏任务

  3. 创建微任务 Promise.then 的回调,并执行script console.log(3); resolve(); console.log(4); 此时输出3和4,虽然resolve调用了,执行了但是整体代码还没执行完,无法进入Promise.then 流程。

  4. console.log(6)输出6

  5. process.nextTick 创建宏任务

  6. console.log(8) 输出8
    第一个过程过后,已经输出了3 4 6 8

第二步:执行异步函数的微任务

  1. 由于其他微任务 的 优先级高于宏任务

  2. 此时微任务 中有两个任务按照优先级process.nextTick 高于 Promise。

  3. 所以先输出7,再输出5

第三步:执行异步函数的宏任务

由于setTimeout的优先级高于setIImmediate,所以先输出2,再输出1

优先级结论

process.nextTick() > Promise.then() > setTimeout > setImmediate;