前端开发中的同步异步编程eventloop

189 阅读2分钟

本文正在参加「金石计划」

一、JS中的同步异步编程

简单来说:

  • 同步 sync:事情必须一件一件的去做,上一件事情没有处理完,下一件事情是无法处理的
  • 异步 async:同时处理多件事情或者是上一件事情哪怕没有处理完,下一件事情也可以继续处理

这里放一张图片,来自于某群一次吵架以后大佬的神总结
cgi-bin_mmwebwx-bin_webwxgetmsgimg_&MsgID=2290332791719943701&skey=@crypt_478cf7f4_646a09f6191c53e208e5224601dfc25a&mmweb_appid=wx_webfilehelper.jpg

二、线程和进程

进程:一个程序(或者浏览器打开一个页面)是开辟一个进程

线程:线程是进程中具体办事的,想同时办多件事,就需要开辟多个线程(一个进程中包含多个线程)

三、浏览器

浏览器是多线程的,可以同时处理很多任务

  • GUI渲染线程:渲染HTML/CSS代码的,最后在页面中绘制出图形「GPU显卡」

  • JS引擎线程:渲染和解析JS的

  • 事件触发线程:监听事件是否触发的

  • 定时器触发线程:监听定时器是否到达时间的

  • 异步HTTP请求线程:用来从服务器获取资源文件或者数据的(link/script/img/ajax...)

  • WebWorker

四、JS

4.1 JS异步

因为浏览器只会分配一个线程(JS引擎线程)去渲染和解析JS,所以在JS中大部分代码都是同步的

JS中有部分异步编程的代码

  • 异步微任务

    • requestAnimationFrame 「有争议」
    • Promise.then/catch/finally
    • async/await
    • queueMicrotask 创建微任务
    • IntersectionObserver
    • MutationObserver
    • process.nextTick「Node」
    • ...
  • 异步宏任务

    • 定时器 setTimeout/setInterval
    • 事件绑定/队列
    • XMLHttpRequest(ajax)/Fetch
    • MessageChannel
    • setImmediate「Node」

setImmediate Node中新提供的定时器 [异步宏任务]

  • 在同步代码中设置的setImmediate ,等价于setTimeout的时间设置为零
  • 但是如果是在某一个异步任务执行的时候设置的setImmediate 则它是异步宏任务队列中优先级最高的
  • ...
// 循环是同步的
console.time('AA');
for (let i = 0; i < 999999; i++) {}
console.timeEnd('AA');
console.log('OK'); 

// 避免死循环:死循环会占用JS引擎线程,导致这个线程无法再去处理其它的程序
while (1) {}
console.log('OK'); 

console.log(1);
setTimeout(() => {
    console.log(2);
}, 0); //浏览器有最快的反应时间,定时器“时间因子(设定的等待时间)”设置为0也不是立即执行,谷歌下最少也要等5~7MS(IE需要等待10~17MS)
console.log(3);
for (let i = 0; i < 99999999; i++) {} //循环完大概需要100MS
console.log(4); 

4.2 两个队列

  • 浏览器为了在JS这种单线程操作中,实现出异步的效果,默认提供两个队列

    • WebAPI 任务监听队列【监听任务是否执行】

    • EventQueue 任务/事件等待队列【能够执行的异步任务 都会在这个队列中 排队等待执行】

4.3 EventLoop事件循环机制

  • 1)因为JS是单线程的,所以一次只能做一件事情;当我们把异步任务放在WebAPI中去监听的时候,此时主线程继续渲染同步的JS代码【单线程异步操作】

  • 2)在同步代码还没有执行完成之前(主线程还没空闲下来),哪怕某个异步任务可以执行了,也不会立即执行这个异步任务,而是把它放置到EventQueue中 排队执行!"只有同步代码执行完成,才会执行异步的任务"

  • 3)当同步任务执行完,主线程空闲下来之后,会去EventQueue中,查找能够执行的异步任务,把其放到栈内存中,让主线程去执行(此时主线程又开始忙了);等到这个异步执行完,再去EventQueue中找其余的异步任务,拿过来执行...这就是JS中非常重要的机制"EventLoop事件循环机制"

    • 是先找微任务执行;可执行的微任务没有了,再去找异步的宏任务

    • 同等级的任务,谁先放进来,就先执行谁

五、思考题

5.1第一题

setTimeout(() => {
    console.log(1);
}, 20);
console.log(2);
setTimeout(() => {
    console.log(3);
}, 10);
console.log(4);
console.time('AA');
for (let i = 0; i < 90000000; i++) {
    // do soming
}
console.timeEnd('AA'); //=>AA: 79ms 左右
setTimeout(() => {
    console.log(6);
}, 8);
console.log(7);
setTimeout(() => {
    console.log(8);
}, 15);
console.log(9);

简单图解

具体图解

EventLoop.png

5.2第二题

async function async1() {

console.log('async1 start');

await async2();

console.log('async1 end');

}

async function async2() {

console.log('async2');

}

console.log('script start');

setTimeout(function() {

console.log('setTimeout');

}, 0)

async1();

new Promise(function(resolve) {

console.log('promise1');

resolve();

}).then(function() {

console.log('promise2');

});

console.log('script end');

图解

EventLoop1.jpg

5.3第三题

let body = document.body;

body.addEventListener('click', function () {

Promise.resolve().then(() => {

console.log(1);

});

console.log(2);

});

body.addEventListener('click', function () {

Promise.resolve().then(() => {

console.log(3);

});

console.log(4);

});

图解

EventLoop2.jpg