前言
在现代的Web开发中,深入理解进程与线程的概念,以及它们在浏览器中的应用至关重要。本文将探讨这些基本概念及其对JavaScript运行方式的影响,以及基于事件循环机制对代码执行顺序进行分析。
进程与线程:基础概念
- 进程:进程是系统进行资源分配和调度的基本单位,是CPU运行指令和保存上下文所需的时间的集合。每个进程都有自己的独立内存空间。
- 线程:线程是进程的一个实体,是CPU调度和分派的基本单位。它比进程更小,描述的是一段指令执行所需的时间。线程共享其所属进程的资源。
浏览器中的进程和线程
当在浏览器中新开一个Tab页面时,浏览器实际上是创建了一个新的进程。这个进程包含多个线程,协同工作以呈现和处理网页。这些线程包括:
- 渲染线程(GPU) :负责页面渲染。
- HTTP请求线程:处理网络请求。
- JavaScript引擎线程:执行JavaScript代码。
值得注意的是,渲染线程和JavaScript引擎线程是互斥的,这意味着当JavaScript代码执行时,页面渲染会暂停。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
//一堆代码xxx
</script>
</head>
<body>
<div class="box"></div>
//一堆代码
</body>
</html>
假设在这段html代码中的js代码部分,对DOM结构的样式进行了修改,而在下方的html代码中也对同一个DOM结构的样式进行了修改,就会引起冲突,所以当JavaScript代码执行时,页面渲染会暂停。
JS:一个单线程的语言
JavaScript是一个单线程的语言。在最初设计时,JavaScript主要用作浏览器脚本语言,考虑到降低功耗和减少性能占用,选择了单线程模型。单线程模型有其独特的优势:
- 节省内存:不需要像多线程那样分配大量线程资源。
- 无锁的概念:简化了程序设计,不需要处理复杂的线程同步问题,也减少了上下文切换的时间。
异步编程:宏任务与微任务
JavaScript的异步编程可以分为宏任务(macrotask)和微任务(microtask)。
- 宏任务:包括script(整体代码), setTimeout, setInterval, setImmediate(在DOM结构加载完毕后执行的操作), I/O操作, UI渲染等。
- 微任务:例如promise.then()(promise本身是同步的,但其then方法是异步的),MutationObserver(监听DOM变更),process.nextTick()。
Event Loop:事件循环机制
JavaScript的核心是基于事件循环的。它的工作原理是:
- 首先执行所有的同步代码(这是一个宏任务)。
- 将执行过程中遇到的宏任务代码和微任务代码分别放入宏任务队列和微任务队列。
- 当调用栈为空时,查看是否有异步代码需要执行。
- 执行所有的微任务。
- 如有必要,进行UI渲染。
- 执行下一个宏任务,开启下一轮事件循环。
接下来我们来分析一段代码的执行顺序:
console.log('script start')
async function async1() {//async声明的函数返回的是一个promise对象,执行使相当于同步代码
await async2() //浏览器给await开小灶,相当于紧跟在await后面的代码变为同步代码
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function () {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function () {
console.log('promise1')
})
.then(function () {
console.log('promise2')
})
console.log('script end')
- 首先执行第一行的代码,
打印'script start'
- 然后执行函数async1的声明
- 接着执行函数async2的声明
- 执行async1函数的调用,
打印'async2 end'
,将console.log('async1 end')
放入微任务队列 - 将setTimeout放入宏任务队列
- 执行promise函数的逻辑,
打印'Promise'
- 将第一个
.then
中的console.log('promise1')
放入微任务队列 - 将第二个
.then
中的console.log('promise2')
放入微任务队列 打印'script end'
- 按进入队列的顺序执行微任务队列(先进先出),
打印'async1 end'
,打印'promise1'
,打印'promise2'
- 执行宏任务队列中的
打印'setTimeout'
运行结果如下:
总结
通过深入理解进程与线程的概念,以及JavaScript的单线程特性和事件循环机制,开发者可以更有效地利用这些知识来编写高效且可维护的代码。实际的代码示例不仅帮助我们理解理论概念,而且还提供了实际应用的见解。希望这篇文章能够帮助您在JavaScript编程的旅程中迈出坚实的一步。
有什么说的不对的地方欢迎在评论区批评指正~
如果觉得写的不错,麻烦点个免费的赞吧!谢谢大家!
原文链接:https://juejin.cn/post/7320169913031049268 作者:雾野千里