前言:JavaScript的事件循环机制或是异步编程是具体代码编写的重点,JavaScript语言的单线程执行特点以及同步任务和异步任务,由这些语言特性共同有一套事件循环执行机制也就是异步代码的预期执行效果。
一、JS代码执行机制
JavaScript的单线程
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。这样所导致的问题是: 如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
同步任务和异步任务
单线程导致的问题就是后面的任务等待前面任务完成,如果前面任务很耗时(比如读取网络数据),后面任务不得不一直等待。为了解决这个问题,利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制。于是JS 中出现了同步任务和异步任务。
JS中所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
- 【同步任务】指的是:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
- 【异步任务】指的是:不进入主线程、而进入“任务队列”的任务,当主线程中的任务运行完了,才会将异步任务相关的回调函数从”任务队列”取出异步任务放入主线程执行。
异步任务又分为宏任务和微任务
常见的宏任务有:
- setTimeout
- setInterval
- I/O(磁盘读写、网络通信···)
- setImmediate(浏览器环境没有、Nodejs环境有)
- requestAnimationFrame(浏览器环境有、Nodejs环境没有)
常见的微任务有:
- Promise
- Object.observe
- process.nextTick (浏览器环境没有、Nodejs环境有)
- MutationObserver(浏览器环境有、Nodejs环境没有)
事件循环机制
事件循环机制则是按照上面所描述的JavaScript特性,当即有同步任务又有异步任务情况下,代码的执行先后顺序。
- 碰到同步任务,就先执行执行栈中的同步任务
遇到函数的嵌套调用就把函数压入栈内再依次“剥洋葱”
- 碰到异步任务就压入任务队列(异步任务分为宏任务和微任务)
- 当前执行栈中的所有同步任务执行完毕,就将执行异步任务,异步任务执行原则“先微后宏”
由于主线程不断地重复获得任务、执行任务、再获取任务、再执行……,这种机制被称为事件循环(Event Loop)
二、一些例子
通过一些实际代码例子及其运行的结果来加深对事件循环机制的理解
同步代码+Promise
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve("success");
console.log(2);
});
promise.then((data) => {
console.log(data);
console.log(3);
});
console.log(4);
const promise1 = new Promise((resolve, reject) => {
console.log("promise1");
resolve("resolve1");
});
const promise2 = promise1.then((res) => {
console.log(res);
});
console.log("1", promise1);
console.log("2", promise2);
同步代码+setTimeout+Promise
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3);
});
});
console.log(4);
new Promise((resolve, reject) => {
console.log(5);
resolve();
})
.then(() => {
console.log(6);
setTimeout(() => {
console.log(7);
});
})
.catch(() => {
console.log(9);
});
console.log(8);
setTimeout(() => {
console.log(1);
}, 0);
console.log(2);
const p = new Promise((resolve) => {
console.log(3);
resolve();
})
.then(() => {
console.log(4);
})
.then(() => {
console.log(5);
});
console.log(6);
then的连续回调都属于一个微任务
new Promise((resolve,reject)=>{
console.log(1)
resolve()
}).then(()=>{
console.log(2)
new Promise((resolve,reject)=>{
console.log(3)
resolve()
}).then(()=>{
console.log(4)
}).then(()=>{
console.log(5)
})
}).then(()=>{
console.log(6)
})
new Promise((resolve, reject) => {
console.log(1);
resolve();
})
.then(() => {
console.log(2);
return new Promise((resolve, reject) => {
console.log(3);
resolve();
})
.then(() => {
console.log(4);
})
.then(() => {
console.log(5);
});
})
.then(() => {
console.log(6);
});
new Promise((resolve, reject) => {
console.log(1)
resolve()
}).then(() => {
console.log(2)
new Promise((resolve, reject) => {
console.log(3)
resolve()
}).then(() => {
console.log(4)
}).then(() => {
console.log(5)
})
}).then(() => {
console.log(6)
})
new Promise((resolve, reject) => {
console.log(7)
resolve()
}).then(() => {
console.log(8)
})
同步代码+async await+Promise
await可以看成是Promise的语法糖(实际上是Generate的语法糖),效果和Promise的回调地狱一样,只是代码书写起来类似同步执行代码。
async function async1() {
console.log(1);
await async2();
console.log(2);
}
async function async2() {
console.log(3);
}
console.log(4);
setTimeout(function () {
console.log(5);
});
async1();
new Promise(function (resolve, reject) {
console.log(6);
resolve();
}).then(function () {
console.log(7);
});
console.log(8);
综合例子
async function a1() {
console.log('a1 start')
await a2()
console.log('a1 end')
}
async function a2() {
console.log('a2')
}
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
Promise.resolve().then(() => {
console.log('promise1')
})
a1()
let promise2 = new Promise(resolve => {
resolve('promise2.then')
console.log('promise2')
})
promise2.then(res => {
console.log(res)
Promise.resolve().then(() => {
console.log('promise3')
})
})
console.log('script end')
评论区