js的事件循环机制 even loops[转载]
文章目录
文章参考
eventLoop 介绍
1. js的单线程
我们知道JavaScript的一大特点就是单线程,而这个线程中拥有唯一的一个事件循环
作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题
2. 什么是事件循环呢?
1.所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)
2.主线程之外,如果有异步任务,还存在一个“任务队列”,只要异步任务有了运行结果,就在“任务队列”之中放置一个事件
3.一旦“执行栈”中的所有同步任务执行完毕,系统就会读取“任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待,进入执行栈,开始执行
个人理解:异步任务执行完又会去执行主任务,主任务执行完了再来执行'任务队列'的异步任务
4.主线程不断重复第3步
假定JavaScript同时有两个线程(事件)
一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以js的运行环境决定js本质就只是单线程
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务
那要是前面的代码不走了,后面就不走了。那的等到猴年马月啊。所以不行,
所以js出了个同步和异步的概念
3.同步任务(代码),异步任务(代码)
我们平时说的最多的就是同步代码,异步代码。同步代码先执行。异步代码会延后执行;
不管什么都是借助函数调用栈来执行的。都是调用函数做事的。
如何区分同步代码和异步代码呢?
同步代码
如果在函数返回的时候,调用者就能够得到预期结果(即拿到了预期的返回值或者看到了预期的效果),那么这个函数就是同步的
异步代码
如果在函数返回的时候,调用者还不能够得到预期结果,而是需要在将来通过一定的手段得到或等待一段时间,那么这个函数就是异步的
但是异步执行的代码都不是马上执行的。怎么排一个先后顺序呢?
一般而言,异步任务有以下三种类型
点击事件,如click 点击的时候,代表任务完成,放到主线程之外的任务队列里排队
资源数据加载,如load等,当响应到全部数据的时候,任务完成,去任务队列排队去
定时器,包括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
复制代码走一遍事件循环
每个宏任务都是一个事件
第一遍循环--------。
-
执行栈里面有好多的代码段,从头开始执行
-
同步代码输出来 start 输出
-
宏任务setTimeout放在宏任务队列里挂起来
-
微任务代码Promise后面的两个then promise1 promise2 放在微任务队列
-
同步代码 end 输出
-
根据定义,主线程同步执行完继续找微任务。执行。输出 promise1 promise2
-
微任务队列空了,开始第二个宏任务
第二遍循环--------。
-
执行栈该执行的执行了。空了
-
微任务空了,执行完了
-
去宏任务找,艾玛。发现一个。setTimeout
-
输出 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
-
遇到一个setTimeout 不执行,放在宏任务队列挂起来 标记setTimeout1
-
遇到promise构造函数里的是同步 输出3 then里面的是微任务。挂载微任务队列(里面有宏任务嵌套。不用管,直接放微任务)
-
同步执行完了。找微任务。 输出4。艾玛里面有个宏任务。再放进宏任务。标记setTimeout2
-
同步执行完了。微任务也完了。改下一个宏任务了
第二遍事件循环
-
执行栈里面的方法开始执行
-
没有同步代码
-
微任务队列也没有
-
找宏任务setTimeout1。输出2
-
走下一个宏任务了
第三遍事件循环
-
执行栈里面的方法开始执行
-
没有同步代码
-
微任务队列也没有
-
找宏任务setTimeout2。输出5
-
走下一个宏任务了.。。也没有了 执行了三次事件循环
我们只需记住:
-
当当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件
-
同一次事件循环中,微任务永远在宏任务之前执行。
-
每个宏任务就开始一次事件循环
微宏任务执行顺序比较?
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
第一步:执行完主任务
-
创建setImmediate 宏任务
-
创建setTimeout 宏任务
-
创建微任务 Promise.then 的回调,并执行script console.log(3); resolve(); console.log(4); 此时输出3和4,虽然resolve调用了,执行了但是整体代码还没执行完,无法进入Promise.then 流程。
-
console.log(6)输出6
-
process.nextTick 创建宏任务
-
console.log(8) 输出8
第一个过程过后,已经输出了3 4 6 8
第二步:执行异步函数的微任务
-
由于其他微任务 的 优先级高于宏任务
-
此时微任务 中有两个任务按照优先级process.nextTick 高于 Promise。
-
所以先输出7,再输出5
第三步:执行异步函数的宏任务
由于setTimeout的优先级高于setIImmediate,所以先输出2,再输出1
优先级结论
process.nextTick() > Promise.then() > setTimeout > setImmediate;