概念及问题
JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
为了解决这个问题,利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程。于是,JS中出现了同步和异步。
同步任务
同步任务都在主线程上执行,形成一个执行栈,程序执行的时候,按照顺序依次执行
异步任务
异步任务是通过回调函数实现的,程序执行的时候,异步任务相关添加到任务队列中(任务队列也称为消息队列)。程序会调过某个步骤继续向下执行,一般而言,异步任务有以下三种类型:
1.普通事件:如click、resize等;
2.资源加载:如load、error等;
3.定时器:包括setInterval、setTimeout等;
事件循环
EventLoop
Event Loop
即事件循环,是指浏览器或Node
的一种解决javaScript
单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。
描述了计算机在执行js时候的一个状态(先去执行栈中执行同步代码,然后再执行队列中异步代码)
异步任务相关添加到任务队列中(任务队列也成为消息队列)
一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进行执行栈,开始执行。
异步任务分类
异步任务分为宏任务和微任务两种:
微任务处理完毕后才会处理宏任务,而该异步任务被判定为宏还是微取决于官方的定义。常见分类如下:
-
宏任务:正常的异步任务都是宏任务,最常见的就是定时器(setInterval, setImmediate, setTimeout);script(整体代码) 、用户交互、IO任务;
-
微任务:微任务出现比较晚,queueMicrotask、Promise的then和catch方法、catch、finally
微任务生成方法:
Promise:Promise是一种异步编程的解决方案,它可以将异步操作封装成一个Promise对象,通过then方法注册回调函数,当promise变为resolve或者reject会将回调函数加入微任务队列中。 MutationObserver:MutationObserver是一种可以观察DOM变化的API,通过监听DOM变化事件并注册回调函数,将回调函数加入微任务队列中。 process.nextTick:process.nextTick是Node.js中的一个API,它可以将一个回调函数加入微任务队列中。
宏任务生成方法:
用户交互:用户在页面上进行交互操作(例如点击、滚动、输入等),会触发浏览器产生宏任务来响应用户操作。 网络请求:当浏览器发起网络请求(例如通过 Ajax、Fetch、WebSocket 等方式)时,会产生宏任务来处理请求和响应。 定时器:通过 JavaScript 宿主环境提供的定时器函数(例如 setTimeout、setInterval)可以设置一定的时间后产生宏任务执行对应的回调函数。 DOM 变化:当 DOM 元素发生变化时(例如节点的添加、删除、属性的修改等),会产生宏任务来更新页面。 跨窗口通信:在浏览器中,跨窗口通信(例如通过 postMessage 实现)会产生宏任务来处理通信消息。 JavaScript 脚本执行事件;比如页面引入的 script 就是一个宏任务。
执行顺序代码演示
setTimeout(function(){
console.log(777)
},10)
console.log('000');//第一步
(async ()=>{
console.log(111);//2
await console.log(222);//3,此行执行,当await右边跟随的代码执行完毕的时候,才会执行后面的代码,此时后面的代码(后面3行)也可以算作“内部的微任务”,所以此行执行后执行输出333
await fn()
console.log(444);
console.log(555);
})().then(()=>{
console.log(666);
});
function fn(){
console.log('a')
return new Promise((res,rej)=>{
console.log('b')
res('d')
console.log('c')
})
}
console.log('333');//4
输出:
注意:
promise内部遇到resolve()和reject()调用的时候,会继续执行后面的代码,但是then和reject就直接放入为微任务队列中,等待同步任务执行
但async内部遇到await的时候,只有当await右边跟随的代码执行完毕的时候,才会执行后面的代码,此时后面的代码也可以算作内部的微任务(因为遇到await会将等await执行完成后,执行外面的同步任务,再执行await后面的任务,见下图)
setTimeout(function(){
console.log(777)
},10)
console.log('000');//第一步
(async ()=>{
console.log(111);//2
await console.log(222); //3,此行执行,当await右边跟随的代码执行完毕的时候,才会执行后面的代码,此时后面的代码(237-239行)也可以算作内部的微任务,所以此行执行后执行252行
await fn()
console.log(444);
console.log(555);
})().then(()=>{
console.log(666);
});
async function fn(a){
console.log(a+'a')
await console.log(222222)
return new Promise((res,rej)=>{
console.log(a+'b')
res(a+'d')
console.log(a+'c')
})
}
fn(1).then((e)=>{
console.log(e);
})
console.log('333');