名词解释
JavaScript是单线程的,也就是说,同一个时刻,JavaScript只能执行一个任务,其他任务只能等待(浏览器是多线程的)
- 同步任务:同步任务不需要进行等待可立即看到执行结果,比如console
- 异步任务:异步任务需要等待一定的时候才能看到结果,比如setTimeout、网络请求
事件循环(Event Loop) :是一个在 JavaScript 引擎等待任务、执行任务、进入休眠状态、等待更多任务这几个状态之间转换的无限循环
任务队列(task queue) :
- 一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合,分别有宏任务队列和微任务队列
- 每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列
- 只要异步任务有了运行结果,就在任务队列之中放置一个事件(一个callback)
宏任务和微任务
我们这里把异步任务分为宏任务和微任务(从老外那里翻译过来的,业内面试都是这么分的,大家就这么记就行了)。要严格一点来说,其实应该是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行,微任务也属于宏任务的一部分,具体什么情况可以自行百度)
// 宏任务((macro)task)包括
script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)
// 微任务(microtask)包括
Promise.then
Object.observe
MutationObserver
process.nextTick(Node.js 环境)
你可能会问为什么为什么我们有了宏任务还需要微任务,因为宏任务太耗费性能,而微任务的执行效率很高,所以平常在开发的时候有异步事务就优先考虑微任务
补充说一下单线程的事
- JS 是单线程的,也就是说执行 JS 代码的线程只有一个( JS 引擎线程),我们也叫主线程,那为什么浏览器可以同时执行异步任务呢,因为浏览器的渲染进程是多线程的,当需要执行异步任务的时候,浏览器会帮我们启动另外一个线程来执行该任务!
- 浏览器中还有定时器线程、HTTP 请求线程等,这些任务不是用来执行 JS 代码的,主要用来执行其他的一些任务!
- 比如主线程(JS 引擎线程)中碰到 AJAX 请求,就把这个任务交给 HTTP 请求线程去真正的发送请求,等到请求回来了之后,再将 callback 里需要执行的 JS 回调函数,交个 JS 引擎线程去执行,也就是说浏览器才是真正执行发送请求这个任务的角色,而 JS 只是负责执行最后的回调处理!
- 所以这里的异步不是 JS 自身实现的,其实是浏览器为其提供的能力。
- V8 引擎不会将微任务交给浏览器的其他线程处理,而是存在自己的一个队列中。
补充说一句,想要实现多线程,可以借助web worker技术,具体自行百度
执行流程
- js代码开始执行后,主线程执行栈中会把任务分为两类.
- 一类是同步任务, 一类是异步任务; 主线程执行栈优先执行同步任务
- 异步任务会被放入特定的处理程序中,满足条件(有执行结果)后,被放到消息(任务/事件)队列中(队列满足FIFO先进先出规则)
- 主线程执行栈中所有的同步任务执行完毕之后,通过事件循环去消息(任务/事件)队列中挑选优先满足条件的程序,放入主线程执行栈中执行。事件循环,周而复始,一直执行
而异步任务又分为宏任务和微任务,主线程执行栈中执行过程中会产生微任务,微任务优先于宏任务之前执行,所以执行顺序是同步代码-->微任务-->宏任务
这里微任务执行完毕后通常会由GUI线程接管dom的渲染工作,然后再开启下一个宏任务,进行下一次的loop
几个执行顺序的小案例
这几个案例,涵盖了js事件执行机制的内容,同时也有很多js你未关注过的知识点,先提前说明一下,建议看完再去测试
- 遇到同步代码就直接执行,而宏任务和微任务产生就先加到了各自的事件队列中(加入但并不一定执行)不管是同步代码,宏任务还是微任务的执行过程中又会产生这三者中的某一个某几个,记得同步立即执行,异步加入队列(没有同步了优先执行微任务,再执行宏任务即可,记得队列的FIFO先进先出)
const res = await fn()这句代码的执行流程
// 先是执行fn()方法,然后就开启一个微任务,
// res就是then方法回调里面的结果,所以res和下面的log都属于微任务里面的代码
const res = await fn()
console.log(1)
- 关于定时器的参数问题,可以放一个code或者一个function
function func(num) {
return function () {
console.log(num)
};
}
// 1. 第一种写法
setTimeout(func(1))
// 表示,这里的func(1)会立即执行,返回一个函数,开启一个宏任务
setTimeout(function () {
console.log(1)
})
// 2.第二种写法 也是我们的常见写法
setTimeout(func,1000)
//也表示开启了一个宏任务(异步事务)
setTimeout(function (num) {
return function () {
console.log(num)
};
})
- then和catch方法执行完毕后会返回一个新的Promise对象并且状态是fulfilled(resolved)
测试一 (小试牛刀)
setTimeout(() => {
console.log('1')
}, 0)
console.log('2');
new Promise((resolve) => {
console.log('3');
resolve()
}).then(() => {
console.log('4');
}).then(()=>{
console.log('5')
})
console.log('6')
//结果顺序是:2 3 6 4 5 1
测试二 (大展身手)
setTimeout(() => {
// #1
new Promise(resolve => {
resolve();
}).then(() => {
// #7
console.log('test');
});
// #8
console.log(4);
});
new Promise(resolve => {
resolve();
console.log(1)
}).then(() => {
// #2
console.log(3);
Promise.resolve().then(() => {
// #4
console.log('before timeout');
// return Promise.resolve(undefined); // 默认会返回这么一句话
}).then(() => {
// #5
Promise.resolve().then(() => {
// #6
console.log('also before timeout')
})
})
})
console.log(2);
// #3
/*
result: 2 3 before timeout also before timeout 4 test
同
3 √
8 √
微
2 √
4 √
5 √
6 √
7 √
宏
1 √
*/
测试三(大结局)
setTimeout(function() {
// #1
console.log(0);
});
new Promise((resolve, reject) => {
// #2
console.log(1);
resolve();
}).then(() => {
// #3
// 执行此微的时候又会产生两个微
console.log(2);
new Promise((resolve, reject) => {
// #6
console.log(3);
resolve();
}).then(() => {
// #7
console.log(4);
}).then(() => {
// #9
console.log(5);
});
}).then(() => {
// #8
console.log(6);
});
new Promise((resolve, reject) => {
// #4
console.log(7);
resolve();
}).then(() => {
// #5
console.log(8);
});
/* result:1 7 2 3 8 4 6 5 0
同
2 √
4 √
6 √
微
3 √
5 √
7 √
8 √
9 √
宏
1 √
*/
测试四(大大结局)
function func(num) {
return function () {
console.log(num)
};
}
// #1
setTimeout(func(1));
async function async3() {
await async4();
// #3
console.log(8);
}
async function async4() {
// #2
console.log(5)
}
async3();
function func2() {
// #10
console.log(2);
async function async1() {
await async2();
// #12
console.log(9)
}
async function async2() {
// #11
console.log(5)
}
async1();
// #13
setTimeout(func(4))
}
// #4
setTimeout(func2);
// #5
setTimeout(func(3));
new Promise(resolve => {
// #6
console.log('Promise');
resolve()
})
.then(
// #7
() => console.log(6))
.then(
// #9
() => console.log(7));
// #8
console.log(0);
/* result:5 Promise 0 8 6 7 1 2 5 9 3 4
同
2 √
6 √
8 √
10 √
11 √
微
3 √
7 √
9 √
12 √
宏
1 √
4 √
5 √
13 √
*/