1、关于javascript
javascript是一门单线程语言,换而言之,在一段时间内,js只会做一件事,只有等这件事做完了,才会去做另外的事。但是任务分大小,分轻重缓急,如何能提高整体的效率是我们要思考的问题。
2、js的事件循环
比如我们做饭,完成做饭这个流程分为:烧水煮饭->洗菜切菜->炒菜等,难道我们要等烧水煮饭完成后再去做后面的事情吗?要知道“干饭不积极,思想有问题”,任何事情都是难不倒我们聪明的程序猿的。
我们把任务分为同步任务跟异步任务:
(1)同步:前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的。比如做饭的同步做法:我们要烧水煮饭,等水开了(10分钟之后),再去切菜,炒菜。
(2)异步:在做一件事情时,因为这件事会花费很长时间,在做这件事的同时,你还可以去处理其他事情。比如做饭的异步做法:在烧水的同时,利用这10分钟,去切菜,炒菜。
本质区别:这条流水线上各个流程的执行顺序不同
同步任务:同步任务都在主线程上执行,形成一个执行栈
异步任务:JS的异步是通过回调函数实现的,一般而言,异步任务有以下三种类型:
(1)普通事件,如 click、resize等
(2)资源加载,如 load、error等
(3)定时器,包括 setInterval、setTimeout等
为了用最小的学习成本彻底弄懂执行机制,所以我们用导图来说明:
看完这张图后,小朋友,你是否有很多的疑惑?
同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
当指定的事情完成时,Event Table会将这个函数移入Event Queue。
主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
上述过程会不断重复,也就是常说的Event Loop(事件循环)。
话不多说,我们来直接上代码:
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
结果的输出顺序:
script start
promise1
script end
promise2
setTimeout
看到这段代码,我相信很多朋友会拿起键盘,哒哒哒敲下。
让我们来更加详细地了解下,JS执行机制:
(1)先执行执行栈中的同步任务
(2)当有异步任务(回调函数)时,提交给对应的异步进程处理,比如onclick,当你点击了,才会放入任务队列中
(3)一旦执行栈中的同步任务执行完毕,系统就会按次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行。
> console.log(1);
> setTimeout(function(){
> console.log(3);
> },0);
> console.log(2);
> // 结果为 1 2 3
console.log(1);
document.onclick = function(){
console.log('click');
}
console.log(2);
// 3秒之后才会提交到任务队列中
setTimeout(function(){
console.log(3);
},3000);
不点击输出的结果是 1,2,3
3秒之前点击的结果是 1,2,click,3
3秒之后点击的结果是 1,2,3,click
看到这里,js的执行机制你懂了吗?
像极了此时自信满满的你
我们进入正题,除了广义的同步任务和异步任务,我们对任务有更精细的定义:
1、macro-task(宏任务):进入任务栈等待主线程执行的主代码块,包括从异步队列里加入到栈的,如setTimeout,setInterval
2、micro-task(微任务):是异步队列中,在当前这一次宏任务执行完后,页面渲染之前要执行的任务。如Promise,process.nextTick,Object.observe
没有对比就没有伤害
让我们来看看宏任务和微任务的执行过程:
1.主体代码(第一次事件循环开始,所有的script代码)作为宏任务进入任务执行栈,但在主线程执行之前要做一系列操作判断。
2.判断当前任务是同步还是异步,同步的由主线程在任务栈中按先进后出顺序(先局部上下文,再全局上下文)执行,异步判断是宏任务还是微任务。
3.异步中的宏任务放入异步的宏任务event Table(异步队列分两种,宏任务队列和微任务队列,event Table也一样),微任务进入微任务event Table,在回调函数注册之后,再次进入它们对应的队列。
4.当主线程的任务执行完后,会检查微任务队列是否有任务,如果有就执行,如此循环,直到微任务队列没有任务。
5.当前事件的微任务执行完后,开始执行下一次事件,即会执行宏任务队列中的宏任务,如此循环下去,直到没有任务。
下面用代码来深入理解上面的机制:
console.log('---start---');//第一轮主线程
setTimeout(() => {
console.log('setTimeout'); // 将回调代码放入个宏任务队列,第二轮宏任务执行
}, 0);
new Promise((resolve, reject) => {
console.log('---Promise第一轮微任务同步执行---');//第一轮微任务同步执行
resolve()
}).then(()=>{
console.log('Promise.then实例成功回调执行'); // 将回调代码放入微任务队列,第一轮宏任务执行完后立即执行
});
console.log('---end---');//第一轮主线程结束
1、这段代码作为宏任务,进入主线程。
2、先遇到同步代码console.log(),立即执行
3、接下来遇到setTimeout ,将回调代码放入个宏任务队列
4、遇到Promise,它在创建的时候就会立即执行,then函数分发到微任务队列
5、遇到最后一个console.log(), 立即执行
5、主线程上所有代码执行完毕, 查看当前有没有可执行的微任务,执行then的回调。
6、微任务队列空了,执行下一个宏任务
其实不管代码怎么嵌套,怎么复杂,只要我们弄清除了原理,什么都不怕。让我们再来个复杂一点的练练手吧
1 new Promise(function (resolve) {
2 console.log('1')// 宏任务一
3 resolve()
4 }).then(function () {
5 console.log('3') // 宏任务一的微任务
6 })
7 setTimeout(function () { // 宏任务二
8 console.log('4')
9 setTimeout(function () { // 宏任务五
10 console.log('7')
11 new Promise(function (resolve) {
12 console.log('8')
13 resolve()
14 }).then(function () {
15 console.log('10')
16 setTimeout(function () { // 宏任务七
17 console.log('12')
18 })
19 })
20 console.log('9')
21 })
22 })
23 setTimeout(function () { // 宏任务三
24 console.log('5')
25 })
26 setTimeout(function () { // 宏任务四
27 console.log('6')
28 setTimeout(function () { // 宏任务六
29 console.log('11')
30 })
31 })
32 console.log('2') // 宏任务一
结果:1-2-3-4-5-6-7-8-9-10-11-12
初步总结:先执行主线程上的同步代码,再执行微任务队列,后执行宏任务队列。
注意事项:
(1)promise是立即执行的,它创建的时候就会执行,不存在将promise推入微任务中的说法;
(2)resolve()是用来表示promise的状态为fullfilled,相当于只是定义了一个有状态的Promise,但是并没有调用它;
(3)promise调用then的前提是promise的状态为fullfilled;
(4)只有promise调用then的时候,then里面的函数才会被推入微任务中;
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210718002617136.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ2NjEzNDI5,size_16,color_FFFFFF,t_70#pic_center
3、async和await
抛出3个疑问: 1、async是干啥的? 2、await在等啥? 3、await等到了又要干啥?
async/await是对Promise的优化,await后面需要是一个Promise对象,如果不是则会被转成Promise对象
执行顺序:
(1)await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成;
(2)若 Promise 正常处理(Resolved,又称Fulfilled),其回调resolve函数的参数作为 await 表达式的值,继续执行 async function;
(3)若 Promise 处理异常(Rejected),await 表达式会把 Promise 的异常原因抛出;
可能还是会有点懵,话不多说,我们直接上代码
console.log('script start');
async function async1() { //语法糖 async2()执行完毕 才执行下面 会加入在微任务里面
await async2();
console.log('async1 end');
};
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function () {
console.log("setTimeout100")
}, 100)
setTimeout(function () {
console.log("setTimeout")
}, 0)
new Promise(resolve => {
console.log('promise')
resolve()
}).then(function () {
console.log("promise1")
})
.then(function () {
console.log("promise2")
})
上面代码中async1遇到await async2()会等async2()执行完毕再把下面的加入到微任务队列里面,所以先输出的是async2 end,由于下面的promise是立即执行的,所以先输出promise再输出async1 end
Promise和async中的立即执行
我们知道Promise中的异步体现在then和catch中,所以写在Promise中的代码是被当做同步任务立即执行的。而在async/await中,在出现await出现之前,其中的代码也是立即执行的。那么出现了await时候发生了什么呢?
await做了什么,从字面意思上看await就是等待,await等待的是一个表达式,这个表达式的返回值可以是一个promise对象也可以是其他值。很多人以为await会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上await是一个让出线程的标志。await后面的表达式会先执行一遍,将await后面的代码加入到microtask中,然后就会跳出整个async函数来执行后面的代码。由于因为async await 本身就是promise+generator的语法糖。所以await后面的代码是microtask。如果里面没有await,执行起来等同于普通函数;如果没有await,async函数并没有很厉害是不是
有人可能会想async没有await的情况下与其它的同步代码谁先执行的问题,让我们直接上代码
console.log('script start');
async function async2() {
console.log('async2 end')
}
async2()
console.log('abc');
console.log('script start');
async function async1() {
await async2();
console.log('async1 end');
};
async function async2() {
console.log('async2 end')
}
console.log('abc');
async1()
有的小伙伴看到上面可能有点懵了,不是说async也是立即执行的吗,async2 不是先出现了,应该先输出async2 end再输出abc吗?要看清楚哦,上面只有先调用了async1()才会调用async2,而console.log(‘abc’)在async2调用之前执行呢?所以还是要看清楚的,来给你验证下:
console.log('script start');
async function async1() {
await async2();
console.log('async1 end');
};
async function async2() {
console.log('async2 end')
}
async1();
console.log('abc');
解决开头的3个疑问:
1、async是干啥的?
定义异步函数(内部通常有异步操作),返回Promise对象(函数返回Promise→显式返回return的Promise;函数返回非Promise→隐式返回Promise.resolve()包装的return值;)
2、await在等啥? 只能放在async函数中,等待右侧表达式结果(函数→结果=return值;字符串→结果=字符串;)
3、await等到了又要干啥?
阻塞后面的代码,先执行async外部的同步代码,同步代码执行完再回到async内部,拿到运算结果(表达式返回Promise→等待Promise对象fulfilled,再将resolve参数作为表达式的运算结果;表达式返回非Promise→直接作为表达式的运算结果;)