浏览器是多线程的,但javascript是单线程的语言,浏览器只分配一个GUI渲染线程去执行我们的js代码,这说明它同一时间只能做一件事。对于大部分的js代码来讲,上面的代码没有执行完,下面的代码是不能执行,但是可以基于单线程的EventLoop机制(事件循环)实现出异步效果。
一、同步和异步
同步:同步是所有的操作都做完了,才返回给用户,这样等待的时间就比较的长。可以看成是当客户端发送请求给服务端,在等待服务端响应的请求时,客户端不做其他的事情。当服务端做完了才返回到客户端。这样的话客户端需要一直等待。
异步:可以看成是当客户端发送给服务端请求在等待服务端响应的时候,客户端可以做其他的事情,这样节约了时间,提高了效率。
区别:程序中的各个任务是否可以按照顺序执行(异步操作可以改变程序的正常执行顺序)
js中的异步编程:定时器,http网络请示,promise.then,async/await,事件绑定,requestAnimationFrame......。
console.log("1");
setTimeout(function () {
console.log("2")
}, 1000);
setTimeout(function () {
console.log("3")
}, 0);
console.log(4);
// 1 4 3 2
二、异步详解
异步操作的过程:主线程发起一个异步请求,异步任务接收到请求并告知主线程已收到,主线程可以继续执行后面的代码,同时异步操作开始执行,执行完成后通知主线程,主线程收到通知后,执行一定的动作。具体来说分为以下几步:
1、代码自上而下执行,所有的同步任务都在主线程 上执行,形成一个执行栈(Execution Context Stack)
2、主线程之外,还有一个消息队列(它和排队相类似,是一种先入先出的数据结构),异步操作进行任务队列
3、一旦执行栈中所有同步任务执行完后,主线程空闲,就会去任务队列中读取对应的任务
4、主线程不断重复上面的第三步进行事件循环,只要主线程空了,就会去读取“任务队列”。
示例:
setTimeout(() => {
console.log(1);
}, 20);
console.log(2);
setTimeout(() => {
console.log(3);
}, 10);
console.log(4);
console.time('AA');
for (let i = 0; i < 200; i++) {
if (i === 199) {
console.log(5);
}
}
console.timeEnd('AA'); //=>AA: 2ms 左右
console.log(6);
setTimeout(() => {
console.log(7);
}, 8);
console.log(8);
setTimeout(() => {
console.log(9);
}, 15);
console.log(10);
三、事件循环
事件循环:js调控同步和异步任务的机制称之为事件循环。从代码执行的顺序角度上来看,程序最开始是按代码顺序执行的,遇到同步任务,立刻执行,遇到异步任务,异步任务挂起进入任务队列,当主线程内的任务执行完毕为空,主线程空闲的时候,它会去任务队列读取对应的任务,推入主线程执行。这个过程不断重复就是我们通常所说的Event Loop即事件循环。
在事件循环中,每进行一次循环的操作我们称之为tick。
js有两大任务队列分别是宏任务(Macro Task)和微任务(Micro Task)。先执行微任务,再执行宏任务。只要有微任务,就不会先执行宏任务。微任务一般是谁先放置的谁执行,而宏任务是谁先到达的谁先执行。
宏任务:定时器,script(整体代码),setInterval,setTimeout,requestAnimationFrame,I/O,UI交互事件等
微任务:promise.then,async/await,process.nextTick(Node.js 环境)
需要注意的是:
1. 先执行同步任务,再执行异步微任务,然后才是异步宏任务。哪怕同步任务没有执行完,异步任务中有到达条件也要继续等待。
2. 不论什么时候放置的异步微任务,也不论异步宏任务是否到达条件,只要异步微任务存在就永远不会执行异步宏任务。
3. 如果在主线程中有一个死循环,那主线程会一直处理这件事,做不了其它事(后面的同步任务或者是异步微任务异步宏任务)。但是如果是浏览器出现了异常也会阻碍主线程的渲染是抛出异常信息它只会影响下面同步任务不执行,已经放置在EventQueue中的任务是继续执行。
简单来讲,整体的js代码这个macrotask先执行,同步代码执行完后有microtask执行microtask,没有microtask执行。macrotask,如此往复循环
示例一:
console.log("script start");
setTimeout(function () {
console.log("setTimeout");
});
Promise.resolve
.then(function () {
console.log("promise1");
})
.then(function () {
console.log("Promise2");
});
1、遇到console.log('script start');输出‘script start’;
2、遇到setTimeout将其回调函数分发给任务队列中的宏任务;
3、遇到Promise,其then函数被分到任务队列中的微任务记为then1;
4、又遇到了then函数将其分到任务队列中的微任务记为then2;
5、遇到comsole.log('script end');输出‘script end’;
6、此时主线程空闲,执行任务队列中的微任务,首行执行then1,输出‘promise1’;
7、此时主线又空闲,执行任务队列中的微任务,执行then2,输出’Promise2‘;
8、执行完所有的微任务后,再执行任务队列中的的宏任务,输出’setTimeout‘;
setTimeout(() => {
console.log(1); // 异步宏任务
}, 0);
console.log(2); // 同步
try {
console.log(a); //a没有定义 这里报错 如果不用try catch的话
} catch (error) { }
console.log(3); // 同步
setTimeout(() => {
console.log(4); // 异步宏任务
}, 10);
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');