Skip to content

1. 核心概念 (Core Concept)

JavaScript 的单线程模型指的是:运行 JavaScript 代码的线程只有一个,同一时间只能执行一个任务。

2. 为什么需要它? (The "Why")

  • 简化编程模型: 单线程避免了多线程中最常见的同步问题(如死锁、数据竞争),使得程序逻辑更易于理解和编写。
  • 适应浏览器环境: 浏览器最初设计时,DOM 的操作是严格同步的,单线程模型可以避免复杂的同步机制来处理 DOM 的修改,确保 DOM 状态的可见性和一致性。例如,如果两个线程同时尝试修改同一个 DOM 元素,会造成冲突和不确定性。

3. API 与用法 (API & Usage)

由于单线程,JavaScript 的执行是线性的。为了处理耗时操作(如网络请求、定时器、事件处理),浏览器引入了异步机制。这主要依赖以下概念:

  • 任务队列 (Task Queue / Callback Queue): 用于存放异步操作执行完成后的回调函数。
  • 事件循环 (Event Loop): 不停地检查主线程中的调用栈是否为空,如果为空,则从任务队列中取出待执行的回调函数放入调用栈,使其得以执行。
  • 宏任务 (Macrotasks): 宏任务包括 script (整体代码)、setTimeoutsetIntervalsetImmediate(Node.js)、I/O、UI Rendering。
  • 微任务 (Microtasks): 微任务包括 process.nextTick(Node.js)、Promise.then()MutationObserver

事件循环的执行顺序大致为:

  1. 执行当前宏任务(比如整个 script 代码块)。
  2. 执行过程中遇到的同步任务立即执行,异步任务的回调放入对应的任务队列。
  3. 当前宏任务执行完毕。
  4. 检查微任务队列,执行所有可用的微任务(直到微任务队列为空)。
  5. 执行渲染(如果需要)。
  6. 开始下一个宏任务(通常是从宏任务队列中取出一个)。

这是一个基础的事件循环示例概念代码(非真实的浏览器内部实现代码):

javascript
// 简化概念代码,非真实引擎实现
function eventLoop() {
    while (true) {
        // 1. 执行一个宏任务 (例如,从任务队列取出最早的)
        let task = getNextMacrotaskFromQueue();
        if (task) {
            executeTask(task);

            // 2. 执行所有微任务
            while (microtaskQueueIsNotEmpty()) {
                let microtask = getNextMicrotaskFromQueue();
                executeTask(microtask);
            }

            // 3. 渲染 (如果需要)
            if (shouldRender()) {
                 render();
            }
        } else {
             // 没有宏任务了,可以休息一下或者等待新任务
             waitForNewTask();
        }
    }
}

实际开发中,我们通过使用异步 API 来利用浏览器的协作机制:

javascript
console.log('Start'); // 同步任务

setTimeout(function() {
  console.log('setTimeout callback'); // 宏任务回调,进入任务队列
}, 0); // 即使延迟为0,它仍然是宏任务

Promise.resolve().then(function() {
  console.log('Promise then'); // 微任务回调,进入微任务队列
});

console.log('End'); // 同步任务

// 输出顺序: Start -> End -> Promise then -> setTimeout callback

4. 关键注意事项 (Key Considerations)

  • 长时间运行的同步代码会阻塞: 如果主线程中的同步代码执行时间过长(例如,复杂的计算或死循环),会阻塞事件循环,导致页面无响应、无法处理用户输入和定时器等到期。
  • 异步不等于多线程: JavaScript 的异步是通过事件循环和回调机制实现的单线程内的非阻塞行为,而非创建新的线程来并行执行任务。
  • Node.js 环境的差异: Node.js 在事件循环和任务队列处理上与浏览器环境有些差异,例如引入了 process.nextTick(更高优先级的微任务)和 setImmediate(特殊阶段的宏任务)。
  • Web Workers: 浏览器提供了 Web Workers API,允许 JavaScript 创建独立的后台线程执行计算密集型任务,但 Web Workers 不能直接访问 DOM。这是一种在单线程模型之外进行并行计算的方式。

5. 参考资料 (References)

不知道说啥了很无语了