同步和异步
我们都知道代码是一行一行执行的,但是有时候如果前面的代码执需要花更长的时间,所以,我们可以让后面的代码先执行,而不会让页面加载的很慢,然后JavaScript是单线程语言,但是他有同步和异步概念,简要了解同步和异步的概念
- 同步:如果在一个函数返回的时候,调用者就能够得到预期结果,那么这个函数就是同步的;
- 异步:如果在函数返回的时候,调用者还不能够得到预期结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的。 为什么JavaScript是单线程的了,我们就需要来聊聊单线程的好处了
js引擎在JS运行时会阻塞UI的渲染(渲染引擎的工作)==> JS引擎线程和渲染页面的线程是互斥的 因为JS可以修改dom结构,如果JS执行的时候UI线程还在工作,就可能导致不安全的渲染ui,得益于JS就是单线程运行的,可以达到节省运行内存,节约上下文切换的时间
事件循环
- 同步任务和异步任务 所有的同步任务都会在执行栈中,JavaScript在按顺序执行执行栈中的方法时,每次执行一个方法,都会为它生成独有的执行环境(上下文),当这个方法执行完成后,就会销毁当前的执行环境,并从栈中弹出此方法,然后继续执行下一个方法。 所有的异步任务都会放到任务队列中,然后一个一个执行 所以他的执行顺序就是先执行同步任务,等到同步任务执行完后,然后再到任务队列中去执行异步任务
看个简单的例子
console.log('代码开始执行');
setTimeout(function(){
console.log('定时器开始执行')
});
console.log('代码执行结束');
结果是: 代码开始执行->代码执行结束->定时器开始啦,因为setTimeout是异步任务,所以就会后后执行,
在看一个例子
setTimeout(function(){
console.log('2')
});
for(let i =0;i<3000;i++){
console.log(1);
}
console.log(3);
它会输出3000个1之后,在输出3,然后才输出2,这也就说明先执行同步任务,等到同步任务执行完后,然后再到任务队列中去执行异步任务
宏任务与微任务
任务队列还可以分成宏任务月微任务,微任务就是一个跟屁虫,一直跟在当前宏任务后面,代码执行到一个微任务,一个接着一个。
我们常见的宏任务有:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境),ajax,读取文件
微任务有:Promise.then、MutaionObserver、process.nextTick(Node.js 环境); 优先级 process.nextTick > promise.then > setTimeout > setImmediate
我们需要记住这些常见的宏任务与微任务
-
process.nextTick
console.log('代码开始执行');
setTimeout(() => {
console.log('timeout');
}, 0);
Promise.resolve().then(() => {
console.error('promise')
})
process.nextTick(() => {
console.error('nextTick')
})
console.log('代码执行结束');
执行结果 代码开始执行 -> 代码执行结束 -> nextTick -> promise -> timeout
上面提到了process.nextTick(),它是node中新引入的一个任务队列,它会在上述同步阶段结束时,在进入下一个阶段之前立即执行。
-
await
关于async await async
函数返回一个 Promise 对象,可以使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
当await后面的函数执行完毕时,await会产生一个微任务(Promise.then是微任务)。它是执行完await之后,直接跳出async函数,执行其他代码。其他代码执行完毕后,再回到async函数去执行剩下的代码,
经典题
先来看看下面这到经典例子,看看你是否能做出来
console.log('script start')
async function async1() {
console.log('async1'); //同步
await async2()
console.log('async1 end') //异步
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
process.nextTick(() => {
console.error('nextTick')
})
new Promise(resolve => {
console.log('Promise') //同步
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
新版的chrome浏览器中不是如上打印的,因为chrome优化了,await变得更快了,答案是 script start -> async1 -> async2 end -> Promise -> script end -> nextTick -> async1 end -> promise1 -> promise2 -> setTimeout 为什么会是这样的结果了,我们慢慢来分析一下
- 首先执行同步代码,输出
script start
, - 调用
async1
输出async1
, - 调用
async2 输出
async2 end `, - 遇到setTimeout,产生一个宏任务
- 执行Promise,输出
Promise
接着输出script end
同步任务都执行完了,然后去到异步任务队列 - 执行process.nextTick,生第一个微任务
- 执行await后面产生的微任务
async1 end
- 遇到then,产生新的微任务
- 开始执行当前宏任务产生的微任务队列,输出
promise1
,该微任务遇到then,产生一个新的微任务 - 执行产生的微任务,输出
promise2
,当前微任务队列执行完毕 - 最后,执行下一个宏任务,即执行setTimeout,输出
setTimeout
总结:
微任务就是一个跟屁虫,一直跟在当前宏任务后面 ,代码执行到一个微任务,一个接着一个
任务的优先级 process.nextTick > promise.then > setTimeout > setImmediate
执行顺序:
- 首先执行同步代码,
- 当执行完所有的同步代码后,执行栈为空,去查询是否有异步代码需要执行
- 执行所有的微任务
- 当执行完所有的微任务后
- 开始下一轮Event-loop,执行宏任务中的异步代码
同步--> 异步--> 微任务--> 宏任务