0
点赞
收藏
分享

微信扫一扫

Javascript事件循环流程分析


 基础概念

事件循环(Event Loop):事件循环是JavaScript运行时环境中的一个循环机制,它不断地检查调栈用和任务队列。当调用栈为空时,事件循环会首先检查微任务队列,并执行其中的所有任务。只有当微任务队列为空时,事件循环才会检查任务队列,并执行其中的任务。

  • 同步代码首先执行。
  • 宏任务和微任务分别被添加到各自的队列中。
  • 执行栈为空后,先执行所有微任务,再执行一个宏任务。
  • 事件循环不断重复这个过程,直到没有任务需要执行。

调用栈

为什么用栈?想想函数内调用其他函数,要等内部函数执行完成才继续执行剩余的外部函数。

    在JavaScript中,调用栈(Call Stack)是一个LIFO(后进先出)结构,用于管理函数调用及其执行上下文。每当一个函数被调用时,一个新的执行上下文会被创建并推入调用栈中;当函数执行完毕后,其执行上下文会从调用栈中弹出。

以下是调用栈内容如何加入和移除的详细过程:

  1. 全局执行上下文
  • JavaScript代码开始执行时,会首先创建一个全局执行上下文(Global Execution Context)。这个上下文在整个程序的生命周期内始终存在,并且作为调用栈的底部。
  1. 函数调用
  • 当一个函数被调用时,会创建一个新的执行上下文(Function Execution Context),并将其推入调用栈中。
  • 每个执行上下文包含以下三个主要部分:
  • 变量对象(Variable Object, VO):存储变量和函数声明。在ES6之后,这个概念被更现代的词法环境(Lexical Environment)和变量环境(Variable Environment)所替代。
  • 作用域链(Scope Chain)保证对上级作用域中的变量和函数的访问
  • this:函数被调用时绑定的this值。
  1. 执行上下文创建和推入调用栈
  • 创建一个新的执行上下文。
  • 将这个新的执行上下文推入调用栈。
  • 执行上下文中的变量和函数声明会被提升(hoisting)。
  • 如果函数中有参数,参数也会被初始化。
  • this值被确定。
  1. 函数执行
  • 执行函数体内的代码。
  • 如果函数内部调用了其他函数,那么会重复步骤2和3,为被调用的函数创建新的执行上下文并推入调用栈。
  1. 函数完成
  • 当函数执行完毕后,其执行上下文会从调用栈中弹出。
  • 如果函数返回了一个值,这个值会被传递给调用者。
  1. 调用栈为空
  • 当调用栈为空时,JavaScript引擎认为当前代码执行完毕,可能会开始执行事件循环中的任务(如异步回调)。

function outerFunction() {
    console.log('Outer function start');
    
    function innerFunction() {
        console.log('Inner function start');
        // Some code...
        console.log('Inner function end');
    }
    
    innerFunction();
    
    console.log('Outer function end');
}

outerFunction();

调用栈的变化:

  1. 全局执行上下文被推入调用栈。
  2. outerFunction 被调用,创建 outerFunction 的执行上下文并推入调用栈。
  3. outerFunction 打印 Outer function start
  4. innerFunction 被调用,创建 innerFunction 的执行上下文并推入调用栈。
  5. innerFunction 打印 Inner function start 和 Inner function end
  6. innerFunction 执行完毕,其执行上下文从调用栈中弹出。
  7. outerFunction 继续执行,打印 Outer function end
  8. outerFunction 执行完毕,其执行上下文从调用栈中弹出。
  9. 全局执行上下文始终是调用栈的底部,此时调用栈为空。

宏任务和微任务

  •  微任务队列:它专门用于处理如Promiseresolvereject回调、async/await、和MutationObserver等微任务。微任务的优先级高于宏任务。
  • 宏任务队列:用来存储准备好执行的回调函数,比如setTimeoutsetInterval的回调

Promise.resolve().then(() => {
  console.log('outerPromise');
  const innerTimer = setTimeout(() => {
    console.log('innerTimer')
  }, 0)
});

const timer1 = setTimeout(() => {
  console.log('outerTimer')
  Promise.resolve().then(() => {
    console.log('innerPromise')
  })
}, 0)
console.log('run');

Javascript事件循环流程分析_执行上下文

编辑

JavaScript代码示例

javascript复制代码

console.log('Script start');


setTimeout(() => {
console.log('Timeout callback');
}, 0);


Promise.resolve().then(() => {
console.log('Promise callback');
});


console.log('Script end');

事件循环执行过程描述

  1. 全局执行上下文创建
  • 当JavaScript引擎开始执行这段脚本时,它会首先创建一个全局执行上下文。这个上下文包含了全局对象(在浏览器中通常是window对象)以及脚本中声明的所有变量和函数。
  1. 同步代码执行
  • 引擎开始执行全局执行上下文中的同步代码。
  • 首先,打印出'Script start'
  1. 宏任务队列(Macro Task Queue)
  • 当遇到setTimeout时,JavaScript引擎不会立即执行其回调函数,而是将回调函数包装成一个宏任务,并将其添加到宏任务队列中。setTimeout的延迟时间设置为0,但这并不意味着回调函数会立即执行;它只会在下一个事件循环迭代中被执行。
  1. 微任务队列(Micro Task Queue)
  • 当遇到Promise.resolve().then(...)时,then方法中的回调函数会被包装成一个微任务,并添加到微任务队列中。微任务队列中的任务会在当前执行栈为空后、下一个宏任务执行之前被立即执行。
  1. 继续同步代码执行
  • 接下来,打印出'Script end'
  • 此时,全局执行上下文中的同步代码已经执行完毕,执行栈为空。
  1. 微任务执行
  • 在执行下一个宏任务之前,JavaScript引擎会检查微任务队列。由于我们有一个微任务(即Promise的回调函数),引擎会执行这个微任务。
  • 打印出'Promise callback'
  1. 宏任务执行
  • 微任务队列为空后,JavaScript引擎会从宏任务队列中取出下一个任务来执行。在这个例子中,宏任务队列中有一个由setTimeout添加的回调函数。
  • 执行这个回调函数,并打印出'Timeout callback'
  1. 事件循环继续
  • 如果此时没有其他宏任务或微任务需要执行,事件循环可能会等待新的异步事件(如用户输入、网络请求等)来触发新的任务添加到任务队列中。
  • 一旦有新的事件触发,相应的回调函数会被添加到宏任务队列或微任务队列中,并等待事件循环的下一个迭代来执行。

Javascript事件循环流程分析_任务队列_02

编辑

举报

相关推荐

0 条评论