浏览器是多线程的
- GUI渲染线程:渲染和解析页面
- JS引擎线程:渲染和解析JS 浏览器分配一个线程去解析JS,所以JS是单线程的
- 定时器监听线程
- 事件监听线程
- HTTP网络请求线程 同源下,浏览器最多同时分配5-7个HTTP线程(HTTP并发数)
- …web worker 是什么
JS是单线程运行的,所以其中大部分代码都是“同步”的(例如:循环)
- 所以在JS中千万不要写死循环、死递归等操作,这些操作会一直占用JS线程引擎,导致后续其他的程序都无法执行
- 但是JS中也有部分异步操作的代码
[异步微任务]
- requestAnimalFrame
- Promise.then/catch/finally
- async/await
- queueMicrotask 手动创建一个异步的微任务
- MutationObserver 监听当前DOM元素属性值改变
- IntersectionObserver 监听当前DOM元素和可视窗口
- process.nextTick[Node]
[异步宏任务]
- setTimeout/setInterval
- 事件绑定/队列(addEventListener)
- XMLHttpRequest/Fetch
- MessageChannel(消息队列)
- setImmediate[Node]
js完成异步操作: 借用浏览器的多线程,再基于EventLoop事件循环机制,实现的单线程异步效果
setTimeout(() => {
console.log(1);
}, 0);
也不是立即执行定时器,而是要等待5-7ms[浏览器最快处理时间]
setTimeout(() => {
console.log(1);
}, 20);
console.log(2);
setTimeout(() => {
console.log(3);
}, 10);
console.log(4);
console.time('AA');
for(let i = 0; i < 90000000; i++) {
//do something 80ms左右
}
console.log(5);
setTimeout(() => {
console.log(6);
}, 8);
console.log(7);
setTimeout(() => {
console.log(8);
}, 15);
console.log(9);
// 245793168
EventLoop事件循环机制:
浏览器加载页面,除了开辟堆栈内存外,还创建了两个队列
- WebAPI 任务监听队列
- EventQueue 事件/任务队列
首先:当主线程自上而下执行代码过程当中,如果遇到“异步代码”
把异步任务放到WebAPI中去监听! - 浏览器开辟新的线程去监听是否可以执行
- 不会阻碍主线程的渲染,会继续执行同步的代码
其次:当异步任务被监测为可以执行了,也不会立即去执行,而是把其挪至到EventQueue中排队等待执行。 - 根据微任务宏任务放在不同的队列中
- 谁先进来排队的,谁在各自队伍的最前面
PS:对于定时器来讲,设定一个等待时间,到时间后不一定会立即去执行,只有同步代码执行完。
最后,当“同步代码(同步宏任务)”执行完毕(有时把同步任务也当做宏任务,宏任务就分为同步宏任务和异步宏任务),主线程空闲下来,此时会去EventQueue中把正在排队的异步任务,按照顺序取出来执行。 - 异步微任务优先级比较高,不论其任务是先放入的还是后放入的。只要有可执行的异步微任务,永远先执行它!
- 同样级别的任务,是谁先放入[谁先到可执行],谁先执行。
- 要把任务拿到栈中执行,而且是交给主线程去执行,所以只要这个拿出来的任务没有执行完,也不会再去拿其他的任务。(代码执行只能放栈里执行)
情况一:
p.then(onfullfilled,onrejected),已知实例p的状态和值,也不会立即把onfullfilled/onrejected执行,而是创建“异步微任务”先进入WebAPI中,发现状态是成功,则onfullfilled可以被执行,再把其挪至EventQueue中排队等着。
let p = new Promise((resolve) => {
resolve(10);
})
p.then((value) => {
console.log('成功', value);
})
console.log(1);
情况二:
如果还不知道实例p的状态,则先把Onfullfilled/Onrejected存储起来[理解为:进入WebAPI去监听,只有知道实例的状态,才知道谁可以执行];定时器到时间,resolve/reject执行,立即修改实例的状态和值,也决定了WebAPI中监听的方法(onfulfilled/onrejected)哪一个去执行,挪至到EventQueue中,异步微任务队列。等待其他同步代码执行完,再拿出来执行。
let p = new Promise((resolve) => {// executor函数立即执行
setTimeout(() => { //把定时器放到WebAPI中监听 等一秒之后放入EventQueue 异步宏任务
resolve(10);//立即把状态转为成功
console.log(p, 2);//2
}, 1000);
})
p.then((value) => {//还不知道p的状态,先把方法存起来等价于放WebAPI去监听 知道状态之后才能决定执行Onfullfilled还是Onrejected
console.log('成功', value);
})
console.log(1);//输出1
// 1 2 成功 10
情况三:
Promise.resolve(1)
.then(value => {
console.log('成功', value);
return 2;
})
.then(value => {
console.log('成功', value);
})
console.log('外面');
//外面 成功1 成功2
遇到await
- 立即执行其后面代码,看返回的promise实例(如果不是promise实例也会变为promise实例) 是否为成功
- 会把当前上下文中,await下面代码当做异步的微任务
- 进入WebAPI去监听:只有后面实例的状态是成功的,才可以执行
- 可执行则进入到EventQueue中排队等待
const fn = async () => {
console.log(1);
return 10;//返回状态成功 值为10 的实例
}
(async function() {
let result = await fn();
console.log(2, result);//异步微任务 等待await后面的实例 状态为成功 才可以执行 失败不可以执行
})();
console.log(3);
//1 3 2 10
async function async1() {
console.log('async1 start');//2
await async2();
console.log('async1 end');//微1 微任务列表 6
}
async function async2() {
console.log('async2');//3
//默认返回状态为成功 值为undefined的实例
}
console.log('script start');//1
setTimeout(function() {
console.log('settimeout'); //8
}, 0);//宏1 宏任务列表
async1();
new Promise(function(resolve) {//executor函数立即执行
console.log('promise1');//4
resolve();//成功
}).then(function() {
console.log('promise2');//微2 微任务队列 7
});
console.log('script end');//5
练习题1:
console.log('start');//1
let intervalId;
Promise.resolve().then(() => {
console.log('p1');//微1 异步微任务队列1 //2
}).then(() => {
console.log('p2');//微2 异步微任务队列2 //3
})
setTimeout(() => {//宏1 异步宏任务1 //进入栈内存 主线程
Promise.resolve().then(()=>{
console.log('p3');//微3 异步微任务队列3 5
}).then(() => {
console.log('p4');//微4 异步微任务队列4 6
});
intervalId = setInterval(() => { //宏2 异步宏任务2
console.log('interval'); // 7
}, 3000);
console.log('timeout');//4
}, 0);
练习题2
setTimeout(() => {
console.log('a');//宏1 4
});
Promise.resolve().then(()=>{
console.log('b');//微1 监听实例的状态 理解为fullfilled 放入微任务队列中 1
}).then(() => {//微2 监听实例p1的状态 监听到p1的状态 放入微任务队列中
return Promise.resolve('c').then(data => {//微3 立即放入微任务队列中
setTimeout(() => {
console.log('d');//宏2放入宏任务队列 5
});
console.log('f');// 2
return data;//得到p2实例状态 c
});
}).then(data => {//微3 监听实例p2的状态
console.log(data);//3 c
})
// b f c a d
练习题3
function func1() {
console.log('func1 start');//3 8
return new Promise(resolve => {
resolve('OK');
});
}
function func2() {
console.log('func2 start');//4
return new Promise(resolve => {
setTimeout(() => {//宏2 等待10ms后
resolve('OK');
}, 10);
});
}
console.log(1);//1
setTimeout(async () => {//宏1 等待20ms后放入 异步宏任务队列中 宏1
console.log(2);//7
await func1();
console.log(3);//9
}, 20);
for(let i = 0; i < 90000000; i++) {}
console.log(4);//2
func1().then(result => {//微1 等待func1确定实例状态 放入微任务队列中
console.log(5);//6
})
func2().then(result => {//微2 等待func2实例状态
console.log(6);//11
});
setTimeout(() => {//宏3 等待5-7ms放入 异步宏任务队列
console.log(7);//10
}, 0);
console.log(8);//5
//1 4 func1 func2 8 5 2 func1 3 7 6
练习题4
async function async1() {
console.log('async1 start')//2
await async2()
console.log('async1 end')//微1 等待await后面的状态 放入异步微任务中 6
}
async function async2() {
console.log('async2')//3
}
console.log('script start')//1
setTimeout(function () {//宏1
console.log('settimeout')//8
})
async1()
new Promise(function (resolve) {
console.log('promise1')//4
resolve()
}).then(function () {//微2 等待实例状态
console.log('promise2')//7
})
console.log('script end')//5
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// settimeout
练习题5
let body = document.body;
body.addEventListener('click', function() {//宏1
Promise.resolve().then(() => {//微1 检测实例状态为成功再执行 异步微任务队列1
console.log(1);//2 先执行微1 再执行宏2
});
console.log(2);//1
});
body.addEventListener('click', function() {//宏2
Promise.resolve().then(() => {//微2
console.log(3);
});
console.log(4);
})
//2 1 4 3