Js是单线程的,只有一个主线程。一行代码执行的过程中,是不存在同时执行的另一行代码的。就是因为这样,当遇到一个很费时间的计算他就会一直等待,所以有了异步事件的概念。
异步事件
浏览器是怎么处理异步事件呢?
—遇到异步事件时,浏览器另开一个线程,让主线程继续执行,待结果返回后,执行回调函数。
看两个例子:
setTimeout(()=> console.log('first'),1000);
console.log('second');
最终的执行结果是:second,first
看代码可能会觉得因为setTimeout是延迟一秒执行的,所以是console.log的输出比setTimeout快。
看另一个例子:
setTimeout(()=> console.log('first'),0);
console.log('second');
最终的执行结果还是:second,first
明明延时是0,为什么还是console的结果先输出呢?
前置知识
- 调用栈:一个个排队等待执行的函数或者方法(包括同步和异步)
- 事件表:每次调用或者执行一些异步操作时,都会将其添加到事件表。这是一个数据结构,它的目的跟踪事件并将其发送到事件队列。
- 事件队列:是一种类似于堆栈的数据结构,存储执行功能的正确顺序。它从事件表接收函数调用,但是需要以某种方式将它们发送到调用堆栈,这个方式就是事件循环。
- 事件循环:这是一个持续运行的过程,用于检查调用堆栈是否为空。如果它为空,它将查看事件队列。如果事件队列中有正在等待的内容,则将其移至调用堆栈。如果没有,则什么也不会发生。

所以上面的代码就很好解释了~
JavaScript看到setTimeout:“嗯,我应该将其添加到事件表中并继续执行”。执行完console,输出second,调用栈空了,它便去查看事件队列里是否有未执行的,看到了settimeout,把它推到调用栈里执行其回调函数,输出first。
再看一个例子:
setTimeout(() => console.log(4));
new Promise(resolve => {
resolve();
console.log(1);
}).then(() => {
console.log(3);
});
console.log(2);
promise和settimeout都是异步任务,js怎么知道应该先调用哪个呢?
宏任务和微任务
异步任务还分了宏任务和微任务。
宏任务:包括整体代码script,setTimeout,setInterval,setImmediate。
微任务:原生Promise、process.nextTick、 MutationObserver。

所有会进入的异步都是指的事件回调中的那部分代码
也就是说new Promise在实例化的过程中所执行的代码都是同步进行的,而then中注册的回调才是异步执行的。
所以上述例子中先输出的是promise中的console1而不是console中的2,在同步代码执行完成后才会去检查是否有异步任务完成,并执行对应的回调,但js异步有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入eventqueue,然后再执行微任务,将微任务放入eventqueue,实际上这两个queue不是一个queue。往外拿的时候先从微任务里拿回调函数,然后再从宏任务的queue上拿宏任务的回调函数。