仅用一篇文章带你彻底了解js执行机制顺序,看完没收获你来揍我!
文章目录
- 前言
- 一、JS任务和事件循环
- 1.1JS任务
- 1.2事件循环
- 二、JS执行机制 同步任务和异步任务
- 2.1JS执行机制原理
- 2.2小试牛刀题目(有讲解)
- 三、异步任务中的宏任务和微任务
- 3.1微任务和宏任务概念讲解
- 四、微任务中的Promise、async、await
- 4.1 什么是async、await
- 4.2 async await执行顺序(题目以及讲解)
- 4.3 Promise执行顺序(题目以及讲解)
- 4.4 相较于 Promise,async/await有何优势
- 五、大结局(开篇题目讲解)
- 总结
前言
每次写代码的时候,有些点老是不明白执行的先后顺序,虽然有的时候无关紧要
现在让我们在正文开始之前,做道题,试试自己是否真正掌握了js执行机制!
题目:
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("js start");
setTimeout(function () {
console.log("timeout");
}, 0);
async1();
new Promise(function (resolve) {
console.log("promise");
resolve();
}).then(function () {
console.log("then");
});
console.log("js end");
正确答案:
js start
async1 start
async2
promise
js end
async1 end
then
timeout
以下是本篇文章正文内容,和作者一起探索吧!
一、JS任务和事件循环
想必大家都曾听过Js是一门单线程语言,每一个时刻只能执行一个任务(JS引擎在执行任务时,是一个一个执行的,如果有多个任务,则后面的任务只能等待)
1.1JS任务
如果js的任务都是同步任务的话,那么遇到定时器、网络请求等这类型需要延时执行的任务,页面可能会瘫痪,需要暂停下来等待这些需要很长时间才能执行完毕的任务,用户体验就很烂,所以我们需要引入异步任务,让这些执行会很长时间的代码都往后稍稍
同步任务:同步任务不需要进行等待可立即看到执行结果,比如console
异步任务:异步任务需要等待一定的时候才能看到结果,比如setTimeout、网络请求
1.2事件循环
当有任务时:
从最先进入的任务开始执行(第1步)
没有其他任务时:
休眠直到出现任务,然后重新转到第 1 步
二、JS执行机制 同步任务和异步任务
2.1JS执行机制原理
js代码开始执行后,主线程执行栈中会把任务分为两类(同步任务和异步任务)
主线程执行栈优先执行同步任务,异步任务会被放入特定的处理程序中,满足条件后,被放到消息(任务/事件)队列中,主线程执行栈中所有的同步任务执行完毕之后,通过事件循环去消息(任务/事件)队列中,将优先满足条件的程序放入主线程执行栈中执行。事件循环,周而复始。
文字都是生硬难理解的,接下来我放两张经典的图片,配合讲解一个具体的例子!
2.2小试牛刀题目(有讲解)
setTimeout(() => {
console.log('定时器开始啦!定时0秒钟,大家是不是觉得我会立马执行!');
}, 0);
console.log('你们猜错啦!同步任务优先执行!');
setTimeout(() => {
console.log('呜呜呜,我比上一个定时器多延时了一点,在所有同步任务都执行完后,没有优先满足条件,我要最后执行了');
}, 0.1);
代码从上往下执行,先碰到了定时器,主线程执行栈认定它为异步任务,于是把它丢给了Event Table(异步任务特定的处理程序),并且开始计时0秒钟,当0秒钟到了以后就注册定时器的回调函数,并且把函数里的内容放入消息队列当中,当所有的同步代码都执行完后,才会执行消息队列中的内容,异步任务谁先满足条件,谁就优先放入消息队列中,并且优先执行
所以这里的执行顺序是
1.碰到定时器 丢到Event Table,开始计时,满足条件之后再交给消息队列
2.碰到同步任务console,直接出结果 控制台打印
3. 碰到定时器 丢到Event Table,开始计时,满足条件之后再交给消息队列
4. 所有同步任务执行完毕,去消息队列中找有没有满足条件的程序,把它放入主线程执行栈中执行
5. 第一个定时器先满足条件优先执行,第二个随后执行
6. 检测到任务队列都已执行完成,代码执行结束
你们猜错啦!同步任务优先执行!
定时器开始啦!定时0秒钟,大家是不是觉得我会立马执行!
呜呜呜,我比上一个定时器多延时了一点,在所有同步任务都执行完后,没有优先满足条件,我要最后执行了
看到这相信你已经差不多明白了!但还是差了一些特殊的东西!临门一脚!
三、异步任务中的宏任务和微任务
3.1微任务和宏任务概念讲解
以上说了同步任务和异步任务是如何执行的,但其实异步任务中还要细分为宏任务和微任务,微任务的执行顺序在宏任务之前,即使是宏任务优先满足条件,但是微任务可以插队,咱接着讲解!
在ES3 以及以前的版本中,JavaScript本身没有发起异步请求的能力,也就没有微任务的存在,(以前都是由宿主(node、浏览器)发起的)在ES5之后,JavaScript引入了Promise,不需要浏览器,JavaScript引擎自身也能够发起异步任务了。
Tick会触发浏览器渲染,Promise不会触发,所以Promis更加轻量级
(在事件循环中,每进行一次循环操作称为 Tick)
微任务和宏任务的关系就像,今天班主任在班上用嗓门宣布今天放假,和校长在广播里宣布放假一样,宏任务牵扯的资源更多,会引发浏览器重绘,而微任务更加轻量级,微任务的执行顺序在同步任务后,宏任务之前
常用的微任务也就Promise
四、微任务中的Promise、async、await
4.1 什么是async、await
ES7 标准中新增的 async
函数,从目前的内部实现来说其实就是 Generator
函数的语法糖。
它基于 Promise,并与所有现存的基于Promise 的 API 兼容。
async 关键字
-
async
关键字用于声明⼀个异步函数(如async function asyncTask1() {...}
) -
async
会⾃动将常规函数转换成 Promise,返回值也是⼀个 Promise 对象
(帮我们new一个promise对象,并且帮我们抛出值) -
async
函数内部可以使⽤await
await 关键字
-
await
用于等待异步的功能执⾏完毕var result = await someAsyncCall()
-
await
放置在 Promise 调⽤之前,会强制async函数中其他代码等待,直到 Promise 完成并返回结果 -
await
只能在async
函数内部使⽤
4.2 async await执行顺序(题目以及讲解)
Promise是处理异步操作的一种方法(典型案例就是回调地狱),而大家常用的async/await 是基于 Promise 的一种语法糖,它提供了一种更直观和同步化的方式来编写异步代码,接着我们来说说Promise、async、await的执行顺序
async function a (){} async顾明思议就是异步的意思,但是函数被调用时,代码会正常立即执行(就是正常的同步任务),但是当碰到await关键词时,await后面跟着的代码会立即执行,但是await下一行的语句会作为微任务加入到微任务队列中,上代码!
async 函数只有从 await 往下才是异步的开始,并且await后面的代码立即执行,await 之后的代码 相当于.then里面的代码 => 微任务
async function getPromise() {
return '我是getPromise函数';
}
console.log(1);
async function fn() {
console.log(2);
let str = await getPromise();
console.log(str);
console.log(4);
}
fn();
setTimeout(() => {
console.log('我是一个大大的宏任务,我执行的不如微任务快');
}, 0);
console.log(3);
1.同步代码console.log(1)执行
2.async修饰的函数正常执行,console.log(2)
3.遇到了await,await后面的代码立即执行,剩下的被当做微任务丢给了异步处理程序,所以打印str和4的代码被丢进了微任务队列里
4.定时器定时0秒,丢进宏任务里
5.打印3
6.同步任务解决完毕,开始去消息队列里找满足条件的任务,优先完成微任务,
发现有微任务,打印’我是getPromise函数’,打印4
7.微任务执行完,轮到宏任务,打印’我是一个大大的宏任务,我执行的不如微任务快’
答案:
1
2
3
我是getPromise函数
4
我是一个大大的宏任务,我执行的不如微任务快
4.3 Promise执行顺序(题目以及讲解)
最后一个点啦!坚持就是胜利!
那就是promise的执行顺序,promise是这样的,在new 创建实例对象时当做参数传入的函数,是立即执行的,但是后面的then会被加入到微任务队列。
console.log(1);
new Promise((resolve, reject) => {
console.log('同学们,我也是立即执行的!');
resolve('then中的代码是微任务!')
}).then(res => {
console.log(res);
})
console.log(2);
答案:
1
同学们,我也是立即执行的!
2
then中的代码是微任务!
(大家如果对promise不了解,建议先去重新学习下!)
4.4 相较于 Promise,async/await有何优势
- 同步化代码的阅读体验(Promise 虽然摆脱了回调地狱,但 then
链式调⽤
的阅读负担还是存在的) - 和同步代码更一致的错误处理方式( async/await 可以⽤成熟的
try/catch
做处理,比 Promise 的catch
错误捕获更简洁直观) - 调试时的
阅读性
, 也相对更友好
五、大结局(开篇题目讲解)
看到这相信大家一定学有所获,跟着我一起来复盘下吧!
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("js start");
setTimeout(function () {
console.log("timeout");
}, 0);
async1();
new Promise(function (resolve) {
console.log("promise");
resolve();
}).then(function () {
console.log("then");
});
console.log("js end");
1.打印同步代码 console.log(“js start”)
2.开启定时器,丢给浏览器处理等待(浏览器是多线程的)
3.立即执行async1 的代码,console.log(“async1 start”);
4.碰到了await,await后紧跟着的内容立即执行,async2 函数被执行,console.log(“async2”);
5.async1 剩下的内容交给了微任务队列
6.promise function里的内容立即执行, console.log(“promise”);
7.then里的内容加入到微任务队列中
8.打印同步代码 console.log(“js end”);
9.所有同步代码执行完毕,检查是否有异步任务,优先执行微任务
10.依次执行微任务,console.log(“async1 end”);
console.log(“then”);
11.微任务队列为空,开始执行宏任务队列, console.log(“timeout”);
12.检测到任务队列都已执行完成,代码执行结束
答案
// js start
// async1 start
// async2
// promise
// js end
// async1 end
// then
// timeout