JavaScript 单线程原理与异步编程机制

为什么 JavaScript 是单线程?

JavaScript 被设计成单线程,简单来说就是 —— 浏览器里干活儿只能一个接一个排着队来,没法同时多开窗口摸鱼。

举个栗子:

你点按钮 → 网页要弹个提示 → 这时候如果网页还在加载数据 → 弹提示就得等加载完 → 单线程 = 一次只能干一件事

为啥这么设计?

  • 最初网页交互简单(填表单、点按钮),单线程够用。
  • 避免多线程打架(比如两个线程同时改同一个按钮的状态)。

单线程的优点:

  • 开发简单:避免了多线程中的数据竞争、死锁等复杂问题。
  • 调试方便:执行顺序清晰明确,便于排查问题。
  • 适合 I/O 密集型任务:大多数 JS 任务(如事件处理、请求响应)并不需要多核计算资源。

单线程的缺点:

  • 阻塞风险高:一旦有耗时操作(如大数据计算、死循环等),会卡住主线程,导致页面卡顿或无响应。
  • 无法利用多核 CPU:在默认模式下,不能并行计算,浪费了现代多核处理器的能力。

JavaScript 如何实现高并发与多线程?

虽然 JS 是单线程执行模型,但通过浏览器或 Node.js 提供的机制,我们可以实现“伪并发”或“多线程模拟”,主要方式如下:

异步操作(等加载时先干别的)

  • 原理:任务被挂起,等待资源时让出主线程,通过事件队列机制在任务完成后重新调度执行。
  • 常用方式
    • setTimeout / setInterval
    • Promise
    • async/await
    • Ajax / Fetch API

Web Worker(开小号偷偷干活)

  • 开启一个独立的线程运行 JS 脚本,不影响主线程。
  • 适用于大计算任务、离线数据预处理等。
  • 与主线程通信使用 postMessage() / onmessage
// main.js
const worker = new Worker("worker.js")
worker.postMessage("开始计算")
worker.onmessage = (e) => {
console.log("子线程结果:", e.data)
}
// worker.js
onmessage = function (e) {
// 执行密集任务
let sum = 0
for (let i = 0; i < 1e8; i++) sum += i
postMessage(sum)
}

Node.js 中的 Worker Threads

  • 使用 worker_threads 模块在后端实现多线程能力,适合 CPU 密集型场景。

任务拆碎(把大活切成小碎活穿插着做)

  • 利用 requestIdleCallbacksetTimeout 分片处理数据,减少卡顿。

异步与同步的区别

同步(Synchronous)

  • 执行顺序严格,必须等待上一个任务完成后才能执行下一个。
  • 阻塞主线程。
console.log("A")
document.querySelector("button").click() // 阻塞直到点击
console.log("B")

异步(Asynchronous)

  • 后台处理任务,不阻塞主线程,通过回调或事件通知结果。
console.log("A")
setTimeout(() => console.log("B"), 1000)
console.log("C")
// 输出顺序:A -> C -> B

Promise、async 和 await 的理解与使用

Promise

  • 用于封装一个异步操作,避免回调地狱。
  • 有三种状态:pending(等待中)、fulfilled(已完成)、rejected(已拒绝)
  • 通过 .then() / .catch() 链式处理结果。
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true
success ? resolve("数据加载成功") : reject("失败")
}, 1000)
})
} fetchData()
.then((data) => console.log(data))
.catch((err) => console.error(err))

async/await

  • Promise 的语法糖,让异步代码写起来像同步代码。
  • 只能在 async 函数中使用。
  • 使用 try/catch 更方便地处理异常。
async function getData() {
try {
const data = await fetchData()
console.log("结果:", data)
} catch (err) {
console.error("出错了:", err)
}
} getData()

总结

技术/特性 描述
单线程模型 JS 默认仅一个主线程,任务顺序执行
异步操作 不阻塞主线程,通过事件队列执行回调
Web Worker 浏览器中模拟多线程,适合重任务
Node WorkerThreads 后端的多线程计算方案
任务拆分 将大任务拆成小块,分帧执行减轻压力
Promise 管理异步逻辑,避免回调地狱
async/await 让异步代码更像同步,提升可读性

总之,单线程就像收银台只有一个店员,但现代网页用各种办法让这个店员手脚麻利到飞起。

JavaScript 单线程原理与异步编程机制的更多相关文章

  1. JavaScript单线程和异步机制

    随着对JavaScript学习的深入和实践经验的积累,一些原理和底层的东西也开始逐渐了解.早先也看过一些关于js单线程和事件循环的文章,不过当时看的似懂非懂,只留了一个大概的印象:浏览器中的js程序时 ...

  2. 一篇需要膜拜的文篇--Javascript异步编程模型进化(转)

    要我能用得这么熟, 那前端出师了哈. http://foio.github.io/javascript-asyn-pattern/ 改天一个一个亲测一下. Javascript语言是单线程的,没有复杂 ...

  3. 深入理解 Python 异步编程(上)

    http://python.jobbole.com/88291/ 前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它.而使用它的同学,则大多数都停留在知 ...

  4. 理解js异步编程

    Promise 背景 javascript语言的一大特点就是单线程,在某个特定的时刻只有特定的代码能够被执行,并阻塞其它的代码,也就是说,同一个时间只能做一件事. 怎么做到异步编程?回调函数.直到no ...

  5. 《深入理解ES6》笔记—— Promise与异步编程(11)

    为什么要异步编程 我们在写前端代码时,经常会对dom做事件处理操作,比如点击.激活焦点.失去焦点等:再比如我们用ajax请求数据,使用回调函数获取返回值.这些都属于异步编程. 也许你已经大概知道Jav ...

  6. 从CompletableFuture到异步编程设计

    从CompletableFuture到异步编程设计,笔者就分为2部分来分享CompletableFuture异步编程设计,前半部分总结下CompletableFuture使用实践,后半部分分享下Com ...

  7. Python网络编程(4)——异步编程select & epoll

    在SocketServer模块的学习中,我们了解了多线程和多进程简单Server的实现,使用多线程.多进程技术的服务端为每一个新的client连接创建一个新的进/线程,当client数量较多时,这种技 ...

  8. JavaScript异步编程原理

    众所周知,JavaScript 的执行环境是单线程的,所谓的单线程就是一次只能完成一个任务,其任务的调度方式就是排队,这就和火车站洗手间门口的等待一样,前面的那个人没有搞定,你就只能站在后面排队等着. ...

  9. How Javascript works (Javascript工作原理) (四) 事件循环及异步编程的出现和 5 种更好的 async/await 编程方式

    个人总结: 1.讲解了JS引擎,webAPI与event loop合作的机制. 2.setTimeout是把事件推送给Web API去处理,当时间到了之后才把setTimeout中的事件推入调用栈. ...

  10. Javascript异步编程之一异步原理

    本系列的例子主要针对node.js环境,但浏览器端的原理应该也是类似的. 本人也是Javascript新手,把自己这段时间学习积累的要点总结下来,希望可以对同样在学习Javascript/node.j ...

随机推荐

  1. win10 linux子系统的一些想法

    什么是linux子系统 具体的概念网上很多,作为个技术人简单粗暴来说就是微软脑子瓦特,突然爱上了linux, 可以在win10安装linux子系统 子系统不同于虚拟机,但是其优点是安装很快,对于新手和 ...

  2. 使用SOUI4中的STreeView控件

    STreeView控件是一个基于虚表技术实现的高性能树形控件. 和STreeCtrl这种传统的树形控件将数据和控件固定在一起不同,STreeView数据和控件分离,使用一个adapter进行连接. 用 ...

  3. Dummynet简单部署

    本文分享自天翼云开发者社区<Dummynet简单部署>,作者:凸凹 部署流程 ^准备内核版本 ^参看系统内核版本 uname -r 我们需要将ipfw编译成内核模块,请确保ipfw用到的内 ...

  4. 基于Java语言的开源能管平台才是最适合国内的能源管理平台

    在"双碳"战略背景下,能源管理已成为政府.企业实现可持续发展的必经之路.面对市场上各类能源管理平台,为何基于Java语言的开源解决方案才是最佳选择?本文将为您揭晓答案,并向您推荐我 ...

  5. 跟着蚂蚁走,它们知道路:用 ACO-ToT 增强 LLM 推理能力

    跟着蚂蚁走,它们知道路:用 ACO-ToT 增强 LLM 推理能力 利用群体智能和思维树优化解锁高级 AI 推理能力 Salvatore Raieli 作者使用 AI 生成的图片 "我发现, ...

  6. C#下.NET配置文件使用(二)

    app.config 与 Settings.settings 用VC#创建一个GUI程序后,就会有一个 Settings.settings 文件. 一旦我们通过VC#给它添加值后,在工程目录下会生成一 ...

  7. KUKA库卡机器人维修

    KUKA库卡机器人作为生产线上的核心设备,一旦出现KUKA机械手故障,将直接影响整个生产线的运行效率.及时的库卡机器人维修工作不仅能够迅速恢复机器人的工作状态,减少生产停滞时间,还能通过预防性维护降低 ...

  8. OpenLayers change 事件获取当前值

    这里有个 change:resolution 事件 但是事件的内容没有value,只有oldValue Zc {type: 'change:resolution', target: F, key: ' ...

  9. 解决2023新版Edge浏览器页面加载不出来问题

    如果你遇到2023新版Edge浏览器页面无法加载的问题,可以尝试以下几种解决方法: 检查网络连接:确保你的网络连接正常,可以尝试打开其他网页或使用其他应用程序进行网络测试. 清除浏览器缓存:打开Edg ...

  10. 使用PySide6/PyQt6实现Python跨平台GUI框架的开发

    在前面的<Python开发>中主要介绍了FastAPI的后端Python开发,以及基于WxPython的跨平台GUI的开发过程,由于PySide6/PyQt6 在GUI的用途上也有很大的优 ...