JavaScript-- 事件循环(Event Loop)

一、是什么

为什么 JavaScript 在浏览器中有事件循环的机制?

JavaScript 在设计之初便是单线程,即指程序运行时,只有一个线程存在,同一时间只能做一件事。为什么要这么设计,跟JavaScript的应用场景有关。

JavaScript 初期作为一门浏览器脚本语言,通常用于操作 DOM ,如果是多线程,一个线程进行了删除 DOM ,另一个添加 DOM,此时浏览器该如何处理?

为了解决单线程运行阻塞问题,JavaScript用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop)。

事件循环(Event Loop)

在JavaScript中,所有的任务都可以分为:

  • 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行;
  • 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等。

同步任务与异步任务的运行流程图如下:

在这里插入图片描述
从上面我们可以看到,同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就是事件循环。

二、两种任务(宏任务与微任务)

宏任务:遵循先进先出的原则。

常见的宏任务有:

  • script (可以理解为外层同步代码、整体代码);
  • setTimeout / setInterval;
  • postMessage、MessageChannel;
  • setImmediate、I/O(Node.js)

微任务:一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前。

常见的微任务有:

  • new Promise().then()
  • MutationObserver(前端的回溯);
  • process.nextTick(Node.js)

事件循环,宏任务,微任务的关系如图所示:
在这里插入图片描述
按照这个流程,它的执行机制是:

  • 执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中;
  • 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完。

Node中的事件循环和浏览器中的事件循环有什么区别

宏任务的执行顺序:

  1. timer定时器:执行已经安排的 setTimeout 和 setInterval 的回调函数;
  2. pending callback 待定回调:执行延迟到下一个循环迭代 I/O 回调;
  3. idle,prepare:仅系统内部使用;
  4. poll:检索新的 I/O 事件,执行与 I/O 相关的回调;
  5. check: 执行 setImmediate() 回调的函数;
  6. close callbacks:socket.on(‘close’, () => {})

微任务和宏任务在 node 的执行顺序:

Node v10及之前:

  1. 执行完一个阶段中的所有任务;
  2. 执行 nextTick 队列里的内容;
  3. 执行完微任务队列的内容

Node v10以后:

和浏览器的行为统一。

三、async与await

async 是异步的意思,await则可以理解为等待。

放到一起可以理解async就是用来声明一个异步方法,而 await是用来等待异步方法执行。

async

async函数返回一个promise对象,下面两种方法是等效的。

function f() {
    return Promise.resolve('TEST');
}

// asyncF is equivalent to f!
async function asyncF() {
    return 'TEST';
}

await

正常情况下,await命令后面是一个 Promise对象,返回该对象的结果。如果不是 Promise对象,就直接返回对应的值。

不管await后面跟着的是什么,await都会阻塞后面的代码。

上代码:

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');
})
async1();
new Promise(function (resolve) {
    console.log('promise1');
    resolve();
}).then(function () {
    console.log('promise2');
})
console.log('script end');
//script start
//async1 start
//async2
//promise1
//script end
//async1 end
//promise2
//settimeout

分析:

  • 定义 async1 函数,不执行;
  • 定义 async2 函数,不执行;
  • 执行 console.log(‘script start’) 并打印 ‘script start’
  • setTimeout 定时器,放到下一个宏任务(等该次宏任务结束后并清空玩微任务再执行);
  • 执行 async1 函数,并打印 ‘async1 start’ ,然后执行 async2 函数并打印 ‘async2’ ,因为 async2 方法前使用了 await,后面的代码被阻塞了,放入到微任务中等待被执行(await可以理解替换为new Promise(),await 后面的代码可以理解为在 .then() 中被执行);
  • 执行 new Promise 中的代码并打印 ‘promise1’ ,.then() 中的代码进入微任务中待执行;
  • 打印 ‘script end’
  • 接下来按顺序清空第一轮的微任务,打印 ‘async1 end’‘promise2’
  • 最后执行下一轮的宏任务(setTimeout);打印 ‘settimeout’

需要注意的是:
new Promise() 代码中没有执行 resolve() 方法,则就不会执行 .then() 里的代码;
如果.then() 里是一个匿名函数,则匿名函数中的代码放入到微任务中去执行;
如果.then() 里的是语句,则可看成把里面的代码直接放到 new Promise() 主体中直接执行;
如果有多个 Promise 并且有多个 .then ,则 .then 方法需按 Promise 和 then 的顺序分别执行。

console.log(1);
setTimeout(function () {
    console.log(2);
})

new Promise(function (resolve) {
    console.log(3);
    resolve();
}).then(
    console.log(4)
).then(function () {
    console.log(8)
}
).then(function () {
    console.log(11)
}
)

console.log(10);

new Promise(function (resolve) {
    setTimeout(function () {
        console.log(5);
    });
    resolve();
}).then(function () {
    console.log( 6)
});

new Promise(function (resolve) {
    resolve();
}).then(function () {
    console.log(9)
});

setTimeout(function () {
    new Promise(function (resolve) {
        console.log(7);
    });
})
//1、3、4、10、6、9、8、11、2、 5、7

四、结尾

声明: 本文都是摘抄网上一些好的文章整合而成,如有侵权请留言
参考来源

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值