单线程JavaScript为何如此高效

2024年 2月 26日 86.3k 0

什么是js执行机制

JavaScript 的执行机制指的是 JavaScript 代码在运行时的工作方式和顺序。它涉及以下几个关键概念:

  • 单线程:JavaScript 是一门单线程的编程语言,意味着它只有一个主线程用于执行代码。这意味着 JavaScript 中的代码是按顺序执行的,一次只能执行一个任务。
  • 任务队列:JavaScript 通过任务队列来管理要执行的任务。任务队列中存放着各种类型的任务,包括同步任务和异步任务。
  • 事件循环:JavaScript 的事件循环是一个持续运行的过程,它负责监视任务队列并选择下一个要执行的任务。事件循环不断地从任务队列中获取任务并将其交给主线程执行。
  • 同步任务和异步任务:同步任务是按照顺序在主线程上执行的任务,执行一个任务时会阻塞后续任务的执行。异步任务是在主线程上注册并在将来某个时间点执行的任务,执行异步任务时不会阻塞后续任务的执行。
  • 微任务和宏任务:JavaScript 中的任务可以分为微任务和宏任务。微任务是在当前任务执行完毕后立即执行的任务,它们使用微任务队列进行管理。而宏任务是在事件循环的下一轮中执行的任务,它们使用任务队列进行管理。
  • JavaScript 的执行顺序:

  • 执行同步任务,将函数调用和变量分配到调用栈中按顺序执行。
  • 遇到异步任务,如定时器、事件监听等,将其注册到任务队列中,并继续执行后续的同步代码。
  • 当同步代码执行完毕后,主线程会检查微任务队列,依次执行队列中的微任务。
  • 执行完微任务后,主线程会从任务队列中取出一个宏任务执行。
  • 循环执行步骤 3 和步骤 4,直至任务队列和微任务队列都为空。
  • 需要注意的是,JavaScript 中的异步任务通常是通过回调函数、Promise、async/await 等机制来处理。通过合理使用异步任务和任务队列,可以实现非阻塞的代码执行,提高代码的性能和响应能力。JavaScript 的执行机制主要涉及以下几个概念:调用栈、事件循环和任务队列。文字有点单调,看看下面的图理解理解

    图片图片

    让我们通过一个例子来解释这些概念。假设我们有以下代码:

    console.log("Script start");
    
    
    setTimeout(function() {
      console.log("setTimeout");
    }, 0);
    
    
    Promise.resolve().then(function() {
      console.log("Promise");
    });
    
    
    console.log("Script end");

    这段代码的执行机制如下:

  • 首先,开始执行代码,遇到第一行 console.log("Script start"),它会立即打印 "Script start"。
  • 接下来,遇到 setTimeout,它是一个异步函数,会被放入任务队列中,并设置一个定时器。由于定时器时间为 0,所以不会立即执行。
  • 然后,遇到 Promise.resolve().then(),它会创建一个 Promise 对象,并将 .then() 中的回调函数放入微任务队列中。
  • 继续执行下一行,打印 "Script end"。
  • 此时,主线程上的同步代码执行完毕,开始执行微任务队列中的任务。首先执行 Promise 的回调函数,打印 "Promise"。
  • 接着,主线程开始执行任务队列中的任务。由于定时器时间到达,setTimeout 的回调函数被放入任务队列中。
  • 最后,主线程执行任务队列中的任务,打印 "setTimeout"。
  • 综上所述,JavaScript 的执行机制遵循以下步骤:

  • 执行同步代码,将函数调用和变量分配到调用栈中按顺序执行。
  • 遇到异步操作,如定时器、事件监听等,将其注册到任务队列中,并继续执行后续的同步代码。
  • 同步代码执行完毕后,主线程会检查微任务队列,依次执行队列中的微任务(Promise 回调函数)。
  • 执行完微任务后,主线程会从任务队列中取出任务执行,执行完一个任务后再检查微任务队列,如此循环,直至任务队列为空。
  • 需要注意的是,微任务优先级高于任务队列中的任务,所以在执行任务队列中的任务之前,会先执行完所有的微任务。

    现学现用,再看一个例子:

    async function async1() {
    	  console.log("async1 start");
    	  await async2();
    	  console.log("async1 end");
        }
        async function async2() {
            console.log("async2");
        }
        console.log("js start");
        setTimeout(function () {
          console.log("timeout");
        }, 0);
        async1();
        new Promise(function (resolve) {
            console.log("promise");
            resolve();
        }).then(function () {
            console.log("then");
        });
        console.log("js end");

    这段代码的打印顺序如下:

  • "js start":立即打印,表示 JavaScript 代码的开始执行。
  • "async1 start":由于 async1 函数被调用,所以会打印 "async1 start"。
  • "async2":async1 函数中调用了 async2 函数,因此会打印 "async2"。
  • "promise":new Promise 的回调函数立即执行,所以会打印 "promise"。
  • "js end":立即打印,表示 JavaScript 代码的执行结束。
  • "async1 end":由于 async2 函数是一个异步函数,await async2() 表达式会等待 async2 函数执行完毕,然后继续执行下面的代码,所以会打印 "async1 end"。
  • "then":Promise 的 then 方法是异步执行的,所以会在下一个事件循环中执行,因此会打印 "then"。
  • "timeout":由于 setTimeout 的延迟时间为 0,所以会在下一个事件循环中执行,因此会打印 "timeout"。代码的执行顺序是按照同步代码的顺序执行,异步代码则根据事件循环的机制来执行。async/await 会暂停同步代码的执行,并等待异步操作完成后再继续执行后续的代码。
  • 总结

    JS 代码的执行顺序主要为:

  • 同步代码 同步代码(sync code)直接进入执行栈执行。执行顺序按代码书写顺序。
  • 异步任务回调 异步任务(如 setTimeout)进入任务队列。
  • 事件循环 事件循环周期性地从任务队列取出任务,推入执行栈执行。当执行栈为空时,才会取出队列中的任务。
  • 执行栈先进后出 执行栈采用先进后出的方式执行函数。在函数执行完毕后才会执行上层函数。这保证了函数的正确嵌套调用。
  • 微任务优先级高于宏任务
    • 宏任务(macrotask):出于任务队列的任务。比如 setTimeout、setInterval。
    • 微任务(microtask):比如 Promise .then、MutationObserver 。微任务的优先级高于宏任务。所以整个执行顺序可以描述为:
  • 同步代码按顺序进入执行栈执行
  • 异步宏任务进入任务队列
  • 当执行栈清空时,执行微任务
  • 接着执行宏任务
  • 循环往复
  • 相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论