摘要:本文将介绍进程、线程以及JavaScript的单线程特性。我们将详细探讨它们的定义、特点和作用,并重点关注JavaScript中的事件循环机制。
引言
计算机中的进程和线程是操作系统中非常重要的概念,它们对于程序的执行和资源的管理起着至关重要的作用。而JavaScript作为一门广泛应用于Web开发的脚本语言,有着自己独特的特性和限制。本文将以浏览器中的JavaScript执行环境为例,深入探讨进程、线程和JavaScript的单线程特性。
进程和线程
进程指的是cpu在运行指令和保存上下文所需的时间。每个进程都有自己独立的内存空间,它们之间相互隔离。而线程是进程中的更小单位,用于执行程序的指令。一个进程可以由多个线程组成,它们共享进程的资源,包括内存空间和文件等。
在浏览器中,打开一个新的标签页就会创建一个新的进程。而这个进程中又包含多个线程,用于处理不同的任务。例如,渲染线程负责将HTML、CSS和JavaScript转化为可视化的页面,HTTP请求线程负责发送网络请求和接收响应,而JavaScript引擎线程则负责执行JavaScript代码。
JavaScript的单线程特性
与其他编程语言不同,JavaScript是一门单线程的语言,即它在同一时间只能执行一个任务。这是由JavaScript的设计和用途决定的。
优点
-
节约内存:由于只有一个线程,JavaScript不需要为每个线程分配独立的内存空间,节约了内存资源。
-
无锁问题:多线程编程中常见的问题是竞态条件和死锁等锁问题。而JavaScript的单线程特性避免了这些问题,减少了上下文切换的时间。
缺点
- 性能限制:由于只有一个线程,JavaScript的性能受到限制。当执行长时间运行的任务时,会阻塞用户界面的响应。
为了解决这个问题,JavaScript引入了异步编程的概念。
异步编程
在JavaScript中,异步编程是通过事件循环机制来实现的。事件循环(Event Loop)是JavaScript执行环境的核心机制之一。
宏任务和微任务
在事件循环中,任务被分为宏任务和微任务:
-
宏任务:包括script代码、setTimeout、setInterval、setImmediate、I/O操作和UI渲染等。
-
微任务:包括Promise.then、MutationObserver和process.nextTick等。
注意:
- async === return new Promise((resolve, reject) => {})
- await === Promise.then()
详情见:从回调地狱到async/await:JavaScript异步执行的进化之路! - 掘金 (juejin.cn)
Event Loop的执行过程
事件循环的执行过程如下:
-
执行同步代码(宏任务):首先执行当前执行栈中的同步代码。注意:浏览器会给await提速,当成同步代码执行
-
查询异步任务:当执行栈为空时,事件循环会查询是否有需要执行的异步任务。
-
执行微任务:如果存在微任务队列,事件循环会执行微任务队列中的所有任务。
-
渲染页面:如果需要,事件循环将渲染页面,更新用户界面。
-
执行宏任务:执行下一个宏任务。
这个过程会不断重复,形成一个事件循环。
如下:
js
console.log('stard');
async function async1() {
await async2() // 浏览器会给await提速,当成同步代码执行
console.log('async1 end');
}
async function async2() {
console.log('async2 end');
}
async1()
setTimeout(() => {
console.log('setTimeout');
}, 0);
new Promise((resolve) => {
console.log('promise');
resolve()
})
.then(() => {
console.log('then1');
})
.then(() => {
console.log('then2');
})
console.log('end');
-
首先我们执行第一个宏任务中的同步代码
console.log('stard')
,输出了stard
。 -
然后,定义了两个异步函数
async1
和async2
。其中,async1
函数内部使用了await async2()
,这会导致async2
函数被等待执行。 -
调用
async1()
,开始执行async1
函数。在这个过程中,会输出async2 end
,然后将执行权交回给async1
函数。 -
接着,遇到了
setTimeout
函数,它被放入宏任务队列中,等待执行。 -
然后,遇到了
Promise
对象的构造函数,是同步代码,它会立即执行,并输出promise
。由于resolve()
是同步执行的,所以then1
和then2
也会立即被放入微任务队列。 -
继续执行同步代码
console.log('end')
,输出end
。 -
此时,当前执行栈已经空了,开始执行微任务。首先执行微任务队列中的
then1
,输出then1
,然后执行then2
,输出then2
。 -
微任务队列中的任务执行完毕,开始执行下一个宏任务。这时候,执行宏任务队列中的
setTimeout
,输出setTimeout
。
所以,最终的输出结果为:
arduino
stard
async2 end
promise
end
then1
then2
setTimeout
结论
本文介绍了进程、线程和JavaScript的单线程特性。进程指的是cpu在运行指令和保存上下文所需的时间,而线程是进程中的更小单位。JavaScript作为一门单线程语言,在同一时间只能执行一个任务。为了解决单线程的性能限制,JavaScript引入了异步编程的概念,并通过事件循环机制来实现。理解这些概念对于开发高效的JavaScript应用程序至关重要。