javascript运行在单线程里,意思就是js每次只能执行一段代码。js有自己的并发模型,由栈、堆和队列组成。如下图
JavaScript 引擎从顶部开始执行脚本,然后向下工作,创建执行上下文并将函数推入和弹出调用堆栈
如果函数需要很长时间才能执行,那么在函数执行期间,用户将无法在 Web 浏览器上执行任何操作,页面卡死
举个例子🌰
function task(message) {
// 模拟耗时任务
let n = 10000000000;
while (n > 0){
n--;
}
console.log(message);
}
console.log('开始执行');
task('耗时任务');
console.log('结束!');
上面的代码执行,每次执行一个函数入栈,在执行 task
函数的时候,由于是一个耗时函数,会阻塞后面代码的执行,直到 task
函数执行完成后,出栈后继续执行最后的 console.log('结束!')
栈
函数调用形成了一个栈帧
堆
给对象分配内存都在堆里实现的
队列
在js执行的过程中,所有的回调函数都会放到消息队列,当栈为空的时候,就从消息队列中取出函数逐一处理,直到栈为空时消息处理结束,例如 setTimeout 等函数
事件循环
不同于c++等多线程语言,js是单线程,所以需要事件循环来检测栈是否为空,然后把函数或者消息进栈执行,类似于这样实现
while (queue.waitForMessage()) {
queue.processNextMessage();
}
浏览器的并发机制
浏览器提供了Web APIs,事件循环和回调队列,如下图
web APIs:包含ajax,setTimeout,Events等等
回调队列:所有的事件监听都会放到回调队列中去,例如点击,focus事件都放到此队列等待响应
事件循环:监听栈是否为空,然后把回调函数放到栈中执行
console.log(1);
setTimeout(function() {
console.log(2)
}, 0);
console.log(3);
上面代码执行到setTimeout的时候回放到回调队列里面去,然后其他函数从上到下执行完毕,栈为空的时候就把回调函数放到栈中执行,所以结果输出为 1,3,2
回调队列的分类
执行回调队列的过程,浏览器会有两个任务队列,优先执行微任务队列的回调,再执行宏任务队列的回调。
宏任务
script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)
微任务
Promise.then
Object.observe
MutaionObserver
process.nextTick(Node.js 环境)