采用单线程模式工作的原因

众所周知,JavaScript 是以单线程去执行代码。这与 JavaScript 的设计初衷有关,最早 JavaScript 是运行于浏览器端脚本语言,用于实现页面的动态交互。
而实现页面交互的核心是 DOM 操作,这就决定 JavaScript 必需使用单线程模式模型,否则会出现复杂的线程同步问题。

假定 JavaScript 能在多线程模式下工作,就会出现一个线程修改一个 DOM 元素的操作,另一个线程又删除这个 DOM 元素,这时浏览器就无法明确该以哪个线程的工作结果为准。

所以为了避免这种线程同步的问题,从一开始就设计为单线程式作。

单线程

JS 执行环境中负责执行代码的线程只有一个。同一时间只能做一个任务,当出现多个任务时,只能进行排队依次完成。
优点

  • 安全、简单

缺点

  • 遇到特别耗时的任务,会阻塞后面的任务。会出现假死的情况。

:::info 解决0耗时任务阻塞任务的问题,JavaScript 将任务的执行模式分为两种:

  • 同步
  • 异步 :::

    同步与异步

    是指 运行环境提供的 API 是以同步或异步模式的方式工作
    同步 API 如 console.log
    异步 API 如 setTimeout

    同步行为 Synchronous

    对应内存顺序执行的处理器指令,每条指令都会严格按照它们出现的顺序来执行。

程序的执行顺序与代码的编写顺序是一致的。
单线程中大多数代码都会以同步模式来执行,这里的同步是意思不是同时执行,而是排队执行。

  1. console.log('global begin');
  2. function bar() {
  3. console.log('bar task');
  4. }
  5. function foo() {
  6. console.log('foo task');
  7. bar();
  8. }
  9. foo();
  10. console.log('global end');

调用栈 ( JavaScript 执行的工作表 )
把所有的代码加载后,在调用栈中压入一个匿名的调用(可以理解为把所有的代码放入匿名的函数中执行)
Synchronous.pptx

异步行为 Asynchronous

相对同步行业,类似于系统中断,即当前进程外部的实体可以触发代码执行。
不会去等待这个任务的结束才开始下一个任务,对于耗时操作都是开启过后就立即往后执行下一个任务
后续逻辑一般会通过回调函数的方式定义

异步模式的缺点:代码的执行顺序混乱

console.log('global begin');

setTimeout(function timer1() {
    console.log('timer1 invoke');
}, 1800);

setTimeout(function timer2() {
    console.log('timer2 invoke');
  setTimeout(function inner() {
      console.log('inner invoke');
  }, 1000);
}, 1000);

console.log('global end');

Asynchronous & Event loop.pptx
如果调用栈是一个正在执行的工作表,那么消息队列就是待办的工作表。当 JS 执行完成 调用栈 的任务后,就会通过 事件轮询 从 消息队列 中取任务出来继续执行,以此类推。在这过程中随时可以在消息队列中放入一些任务,这些任务会在消息队列当中排队等待 事件轮询。

以住的异步编程模式

回调函数

可以理解为一件你想要做的事情,但你并不知道这件事情依赖的任务什么时候完成。所以最好的办法,把这个事的步骤写到一个函数中交给任务的执行者。
这个任务的执行者知道这个任务是什么时候完成的,那么当任务结束时帮你执行你想做的事。

异步行为是 JavaScript 的基础,但以前实现不理想。早期 JavaScript 只支持定义回调函数来表明异步操作完成。
串联多个异步操作是一个常见的问题,通常需要深度嵌套回调函数(俗称“回调地狱”)来解决。

  1. 异步返回值

给异步操作提供 一个回调,这个回调中包含要使用异步返回值的代码(作为回调的参数)

function double(value, callback) {
    setTimeout(() => callback(value * 2), 1000);
}

double(3, (x) => console.log(`I was given: ${x}`));
  1. 失败处理

异步操作的失败处理在回调模型中也要考虑,因此自然就出现了成功回调和失败回调。

  1. 嵌套异步回调

随着代码越来越复杂,回调策略是不具有扩展性的。“回调地狱”的代码 维护起来就是噩梦。