目录
- js为何是单线程
- 【异步任务】宏任务、微任务
- 代码
- 事件循环 流程图
- 什么是event loop
- JS中的任务和队列
- 执行栈与事件队列
- 【异步任务】宏任务、微任务
- 九 总结
场景举例: 比如,我正在写代码(这是一个同步任务)-> 我饿了,点了一个外卖(外卖还没到的这个阶段我还在执行同步任务写代码)-> 外卖到了吃饭+饮料等等(异步任务中的微任务)-> 继续同步任务写代码
- 同步代码 => 微任务 => 宏任务
一 js为何是单线程 与 异步任务出现的原因
JS是一个单线程的脚本语言。
那么为什么JS一定要是一个单线程的呢?如果像多线程那样效率是不是会有很大提升呢? 答案当然是否定的,
JS为浏览器而生,用户必然要和浏览器存在很多的交互,会在这交互的过程中操作DOM。如果是多线程的话,一个线程要去添加,一个线程要去删除,就会有问题。
对于浏览器来说,单线程一定是比多线程严谨的,那么这样就会有一个问题产生,如果一个操作需要大量的请求时间或者代码进行着大量的计算,我们的页面就会卡死,因为之前的代码根本没有执行结束,不会跑到后面的代码。
二 【异步任务】宏任务、微任务
见下方第8条
- 宏任务:
script、setTimeout、setInterval、I/O、Promise构造函数体内语句then之前的。- 微任务:
Process.nextTick、Promise.then、MutationObserve。
一个宏任务执行结束后会查看微任务队列是否为空而去决定执行微任务还是执行下一个宏任务。
三 代码
1)demo1
console.log('我是script开始!')
setTimeout(() => {
console.log('我是setTimeout!')
}, 0)
new Promise((resolve)=>{
console.log('我是Promise!')
resolve()
}).then(()=>{
console.log('我是Promise.then!')
})
console.log('我是script结束!')
一段代码中在所有微任务执行完毕之前是不会去执行宏任务的!!!
执行结果
2)demo2
console.log('我是script开始!')
setTimeout(() => {
console.log('我是setTimeout!')
}, 0)
new Promise((resolve)=>{
console.log('我是Promise!')
resolve()
}).then(()=>{
console.log('我是Promise.then!')
})
console.log('我是script结束!')
new Promise((resolve)=>{
console.log('我是Promise 1')
resolve()
}).then(()=>{
console.log('then 1')
}).then(()=>{
console.log('then 2')
}).then(()=>{
console.log('then 3')
})
先执行
script中的同步的任务,console.log、Promise。遇到setTimeout将它推到事件列表中同步代码执行结束后查看异步代码执行结束了没有(.then),.then为微任务,到此微任务执行完毕开始执行下一个宏任务setTimeout。
执行结果
3)demo3
加入 async await async/await 本质上是Generator的语法糖,返回一个Promise对象。
await必须在async function 的内部使用。在遇到await的时候,代码会暂停执行,在异步操作完成后才会继续执行。
console.log('script start');
async function async1() {
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2 end');
}
async1();
setTimeout(function() {
console.log('setTimeout');
}, 0);
new Promise(resolve => {
console.log('Promise');
resolve();
})
.then(function() {
console.log('promise1');
})
.then(function() {
console.log('promise2');
});
console.log('script end');
执行结果
注意
上图中 await 后的代码会直接执行。那么 我们将 await 转为 Promise
await async2();
// 下方的 console为何不在这个循环中执行。
console.log('async1 end');
console.log('script start');
async function async1() {
new Promise(res=>{
async2()
res()
}).then(()=>{
console.log('async1 end');
})
}
async function async2() {
console.log('async2 end');
}
async1();
setTimeout(function() {
console.log('setTimeout');
}, 0);
new Promise(resolve => {
console.log('Promise');
resolve();
})
.then(function() {
console.log('promise1');
})
.then(function() {
console.log('promise2');
});
console.log('script end');
这里就需要对同步、异步、宏任务、微任务的执行顺序有一个清楚的认知。
- 1.首先执行同步代码,
script属于宏任务。 - 2.所有同步代码执行完毕,去查看是否有异步代码需要执行。
- 3.如果有的话执行异步代码中的微任务。
- 4.所有微任务执行后,至此第一轮Event Loop就结束了。
- 5.开始新的一轮Event Loop,开始执行异步任务中的宏任务。
四 事件循环 流程图
五 什么是event loop
event loop也就是事件循环,它是为了协调事件(event),用户交互(user interaction),脚本(script),渲染(rendering),网络(networking),用户代理(user agent)的工作而产生的一个机制。
六 JS的运行机制【单线程】
6.1 单线程的JavaScript
JavaScript语言的一大特点就是单线程,也就是说在同一时间只做同一件事。这是基于js的执行环境决定的,因为在浏览器中,有许多的dom操作,如果在同一时间操作一个dom,很容易造成混乱,所以为了避免发生同一时间操作同一dom的情况,js选择只用一个主线程执行代码,来保证程序执行的一致性,单线程的特点也应用到了node中。
6.2 JS中的任务和队列
- 同步任务 【主线程】
- 异步任务【任务队列】
举例子:同步任务 -> 多个ajax请求 -> 宏任务 -> 微任务 -> ...
JavaScript是单线程的,也就意味着所有任务需要排队,前一个任务执行完,才能执行下一个任务,但是因为IO设备(输入输出设备)很慢(比如Ajax从网络读取数据),不得不等待结果返回之后才能继续,这样的执行效率很慢。 于是分成了两种任务来处理,同步任务和异步任务。 同步任务是指在主线程排队的任务,只有前面的任务执行完之后才执行后面的任务。 异步任务指的是任务不进入主线程,而进入到一个任务队列(task queue),主线程的任务可以继续往后执行,而在任务队列里的异步任务执行完会通知主线程。
七 执行栈与事件队列
当javascript代码执行的时候会将不同的变量存于内存中的不同位置:堆(heap)和栈(stack)中来加以区分。其中,堆里存放着一些对象。而栈中则存放着一些基础类型变量以及对象的指针。当所有所有同步任务都在主线程上执行时,这些任务被排列在一个单独的地方,形成一个执行栈
当浏览器js引擎解析这段代码时,会将同步任务顺序加入执行栈中依次执行,当遇到异步任务时并不会一直等待异步任务返回结果再执行后面的任务,而是将异步任务挂起,继续执行同步任务,当异步任务返回结果时,将异步任务的回调事件加入到一个事件队列(Task Queue)当中去,这个事件队列里的任务并不会立即执行,而是等同步任务全部执行完,再依次执行事件队列里的事件。
依次执行同步任务,完成后依次执行事件队列,完成后再去执行同步任务,这样形成了一个循环,就是事件循环(Event Loop)。
八 【异步任务】 宏任务(macro task)与微任务(micro task)
- 宏任务
- 微任务
异步任务又分为宏任务与微任务两种,微任务并不是老老实实的按照事件队列的顺序去执行,而是按照microTask—>macroTask的顺序去执行,先执行完队列中所有的microTask再去执行macroTask
宏任务和微任务的分类
- MacroTask: script(整体代码), setTimeout, setInterval, setImmediate(node独有), I/O, UI rendering
- MicroTask: process.nextTick(node独有), Promises, Object.observe(废弃), MutationObserver
// 举个例子
setTimeout(()=>{
console.log(1)
})
Promise.resolve().then(function() {
console.log(2)
})
console.log(3)
// 执行结果是:3 2 1
// 这是因为事件循环的顺序是:同步代码 => 微任务 => 宏任务
九 总结
场景举例: 比如,我正在写代码(这是一个同步任务)-> 我饿了,点了一个外卖(外卖还没到的这个阶段我还在执行同步任务写代码)-> 外卖到了吃饭+饮料等等(异步任务中的微任务)-> 继续同步任务写代码
- 同步代码 => 微任务 => 宏任务
同步代码->(异步代码中的)所有的微任务->下一轮 Event Loop宏任务....- 同步任务【主线程】,异步任务【任务队列】