核心概念:同步 vs 异步

  1. JavaScript 是一门单线程语言,这意味着它同一时间只能执行一个任务;但单线程会导致一个问题:如果遇到耗时操作 (比如网络请求、定时器、文件读取),程序会被阻塞,页面会卡死;
  2. 为了解决这个问题,JS 设计了同步和异步两种执行模式;

同步

  1. 定义:任务按顺序执行,前一个任务完成后,后一个任务才能开始;

  2. 特点:阻塞执行,代码自上而下逐行运行,每一步都要等上一步结束;

  3. 例子:

    function synchronousExample() {
        console.log('开始同步操作');
    
        // 模拟耗时操作
        const start = Date.now();
        while (Date.now() - start < 3000) {
            // 阻塞3秒
        }
    
        console.log('同步操作完成');
        return '结果';
    }
    
    console.log('调用前');
    const result = synchronousExample(); // 这里会阻塞3秒
    console.log('调用后,结果:', result);
    

异步

  1. 定义:不阻塞主线程的任务,耗时操作会被 “挂起”,等结果返回后再执行回调;

  2. 特点:非阻塞执行,耗时任务不会卡住主线程,主线程继续执行同步任务;

  3. 常见异步场景:

    1. 定时器 (setTimeout、setInterval)
    2. 网络请求 (fetch、axios、XMLHttpRequest)
    3. 事件监听 (click、scroll)
    4. Promiseasync/await
    5. 文件操作 (Node.js 中)
  4. 例子:

    function asynchronousExample() {
        console.log('开始异步操作');
    
        // 使用回调
        setTimeout(() => {
            console.log('异步操作完成');
        }, 3000);
    
        console.log('函数立即返回');
        return '立即返回值';
    }
    
    console.log('调用前');
    const result = asynchronousExample(); // 不会阻塞
    console.log('调用后,立即结果:', result);
    
    // 输出顺序:
    // 调用前
    // 开始异步操作
    // 函数立即返回
    // 调用后,立即结果: 立即返回值
    // (3秒后)异步操作完成
    

事件循环(Event Loop):JS 异步的核心运行机制

  1. 先搞懂 JS 的执行环境结构:JS 运行时会划分不同的 “任务队列”,核心分为:

    1. 调用栈 (Call Stack):也叫执行栈,存放正在执行的同步任务,遵循 “先进后出” 原则;
    2. 任务队列 (Task Queue):也叫宏任务队列,存放待执行的异步宏任务 (比如定时器回调、事件回调、ui 渲染、script 脚本、requestAnimationFrame 等)
    3. 微任务队列 (Microtask Queue):优先级高于宏任务队列,存放微任务 (比如 Promise.then/catch/finally、async/await、queueMicrotask、process.nextTick 等)
  2. 事件循环的完整执行流程

    1. 执行调用栈中的同步任务,直到调用栈为空;
    2. 执行微任务队列中的所有微任务 (按顺序),直到微任务队列为空;
    3. 从宏任务队列中取出第一个宏任务,放入调用栈执行;
    4. 重复步骤 1-3,形成 “循环”;

案例分析

<body>
  <script>
    document.body.style.background = 'green';
    console.log(1);

    Promise.resolve().then(() => {
      console.log(2);
      document.body.style.background = 'red';
    });

    console.log(3);
    // 1 3 2 red(只渲染红色)
  </script>
</body>
<body>
  <script>
    document.body.style.background = 'green';
    console.log(1);

    setTimeout(() => {
      console.log(2);
      document.body.style.background = 'red';
    }, 10);

    console.log(3);
    // 1 3 2 green => red(屏幕闪烁)
  </script>
</body>

面试题

第 1 题

  1. 题目

    let n = 0;
    
    // 设置定时器的操作是同步的,但是 1S 后做的事情是异步的
    setTimeout(_ => {
        n += 10;
        console.log(n);
    }, 1000);
    
    n += 5;
    console.log(n);
    
  2. 图解

第 2 题

  1. 题目

    setTimeout(() => {
      console.log(1);
    }, 20);
    
    console.log(2);
    
    setTimeout(() => {
      console.log(3);
    }, 10);
    
    console.log(4);
    
    // 计时开始
    console.time('AA');
    for (let i = 0; i < 90000000; i++) {
      // do soming  280ms左右
    }
    // 计时结束
    console.timeEnd('AA');
    
    console.log(5);
    
    setTimeout(() => {
      console.log(6);
    }, 8);
    
    console.log(7);
    
    setTimeout(() => {
      console.log(8);
    }, 15);
    
    console.log(9);
    
  2. 图解

第 3 题

console.log(1);

setTimeout(_ => console.log(2), 50);

console.log(3);

setTimeout(_ => {
  console.log(4);
  // 遇到死循环,所有代码执行最后都是在主栈中执行,遇到死循环,主栈永远结束不了,后面啥都干不了
  while (1 === 1) { }
}, 0);

console.log(5);

第 4 题

<body>
  <button id="button">按钮</button>
  <script>
    button.addEventListener('click', () => {
      console.log('listener1');
      Promise.resolve().then(() => console.log('micro task1'))
    })
    button.addEventListener('click', () => {
      console.log('listener2');
      Promise.resolve().then(() => console.log('micro task2'))
    })
    
    button.click(); // 相当于函数执行 click1() click2(),此时并未将回调放到 宏任务队列中
    // listener1
    // listener2
    // micro task1
    // micro task2
  </script>
</body>
<body>
  <!-- 点击按钮 -->
  <button id="button">按钮</button>
  <script>
    button.addEventListener('click', () => {
      console.log('listener1');
      Promise.resolve().then(() => console.log('micro task1'))
    })
    button.addEventListener('click', () => {
      console.log('listener2');
      Promise.resolve().then(() => console.log('micro task2'))
    })
    // 点击按钮执行事件,将两个事件回调都放到了宏任务队列中,每次拿出一个执行
    // listener1
    // micro task1
    // listener2
    // micro task2
  </script>
</body>
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 核心概念:同步 vs 异步
    1. 1.1. 同步
    2. 1.2. 异步
  2. 2. 事件循环(Event Loop):JS 异步的核心运行机制
  3. 3. 案例分析
  4. 4. 面试题
    1. 4.1. 第 1 题
    2. 4.2. 第 2 题
    3. 4.3. 第 3 题
    4. 4.4. 第 4 题