js【深度解析】代码的执行顺序

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: js【深度解析】代码的执行顺序

代码的分类

我们将每一句要执行的 js 代码当做一个任务,则 js 代码可以按照其执行方式的不同,按下图分类


  • 同步任务:立即执行的代码
  • 异步任务:延迟执行的代码
  • 微任务:被放入微任务队列(micro task queue)中等待执行的代码

因为Promise、async 、await 都是 ES6 语法定义的

  • 宏任务:被放入 Web APIs 中等待执行的代码

因为 setTimeout 、setInterval、ajax、Dom事件都是浏览器定义的

不同类型代码执行的顺序和过程

1. 同步任务

  1. 将其放入调用栈(Call Stack)中
  2. 执行该段代码
  3. 将其从调用栈中移除

除 Promise、async 、await、setTimeout 、setIntervall、ajax、Dom事件之外的代码都是可立即执行的同步任务

2. 微任务

  1. 将其放入微任务队列中等待执行
  2. 待所有同步任务执行完毕,开始按微任务队列依次执行微任务
    将第1个进入微任务队列的微任务放入调用栈中,执行微任务内的代码,将其从调用栈中移除,再将第2个进入微任务队列的微任务放入调用栈中,执行,移除…… 以此类推,直到清空微任务队列。

3. DOM 渲染

微任务队列清空后,便暂停 js 代码的执行,开始尝试渲染 DOM, 若没有 DOM操作,则跳过此步。

因 JS 可修改 DOM 结构 , 所以 js 代码的执行和 DOM 渲染必须共用一个线程,这便导致 js 代码的执行和 DOM 渲染无法同时进行。

4. Event Loop 事件轮询

DOM渲染完毕后,便触发Event Loop,开始事件轮询

5. 宏任务

  1. 将其放入 Web APIs 中,并开始计时
  2. 计时结束后,将其放入回调队列(Callback Queue)
  3. 回调队列中的宏任务,依次被事件轮询到
    将第1个进入回调队列的宏任务放入调用栈中,执行宏任务内的代码,将其从调用栈中移除,再将第2个进入回调队列的宏任务放入调用栈中,执行,移除…… 依此类推,直到清空回调队列。

最终 js 代码的执行顺序

因不同类型的代码可能层层嵌套,所以最终 js 代码的执行顺序可能非常复杂,但总的运行方式,如上文所言,根据代码的不同,将其放入不同的队列或栈中,然后依次执行,核心要领在于

  1. 依次执行可执行的同步任务,直到清空调用栈
  2. 依次执行微任务队列中的微任务,直到清空微任务队列
  3. 暂停 js 代码的执行,尝试渲染DOM,若无DOM操作,则直接进入第4步,若有DOM操作,则待DOM渲染完成,进入第4步
  4. 开始事件轮询,依次执行回调队列中的宏任务(事件轮询会一直进行下去,一旦有新的宏任务计时结束进入回调队列,就会被送去调用栈执行)

简单概括如下图所示:

自测题 1

console.log(100)

// 宏任务
setTimeout(() => {
    console.log(200)
})

// 微任务
Promise.resolve().then(() => {
    console.log(300)
})

console.log(400)

答案 100 400 300 200

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}

async function async2() {
  console.log("async2");
}

console.log("script start");

setTimeout(function () {
  console.log("setTimeout");
}, 0);

async1();

new Promise(function (resolve) {
  console.log("promise1");
  resolve();
}).then(function () {
  console.log("promise2");
});

console.log("script end");

答案

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

解析:详见代码注释

// 定义函数,先跳过
async function async1() {
  console.log("async1 start"); // 2
  await async2(); // 先执行 async2() , 进入 async2 函数内

  // await 后的代码都是微任务,将其放入微任务队列 --- 微任务 1
  console.log("async1 end"); // 6
}

// 定义函数,先跳过
async function async2() {
  console.log("async2"); // 3
}

// 同步任务
console.log("script start"); // 1

// 宏任务 1
setTimeout(function () {
  console.log("setTimeout"); // 8
}, 0);

// 同步任务
async1(); // 进入 async1 函数内

// 同步任务 -- Promise 函数体内的代码会立刻执行
new Promise(function (resolve) {
  console.log("promise1"); // 4
  resolve(); // Promise 状态变为 resolved , 立即触发了 then 函数
}).then(function () {
  // then 函数是个微任务,将其放入微任务队列 --- 微任务 2
  console.log("promise2"); // 7
});

// 同步任务
console.log("script end"); // 5

// 同步任务执行完毕,开始执行微任务
// 依次执行微任务1 和 微任务 2
// 最后执行宏任务

自测题 2

console.log('start')
setTimeout(() => {
    console.log('a')

    Promise.resolve().then(() => {
        console.log('c')
    })
})
Promise.resolve().then(() => {
    console.log('b')

    setTimeout(() => { console.log('d')})
})
console.log('end')

答案

start 
end 
b 
a 
c 
d

目录
相关文章
|
15天前
|
JavaScript 前端开发 测试技术
在 golang 中执行 javascript 代码的方案详解
本文介绍了在 Golang 中执行 JavaScript 代码的四种方法:使用 `otto` 和 `goja` 嵌入式 JavaScript 引擎、通过 `os/exec` 调用 Node.js 外部进程以及使用 WebView 嵌入浏览器。每种方法都有其适用场景,如嵌入简单脚本、运行复杂 Node.js 脚本或在桌面应用中显示 Web 内容。
50 15
在 golang 中执行 javascript 代码的方案详解
|
15天前
|
PHP 开发者 容器
PHP命名空间深度解析:避免命名冲突与提升代码组织####
本文深入探讨了PHP中命名空间的概念、用途及最佳实践,揭示其在解决全局命名冲突、提高代码可维护性方面的重要性。通过生动实例和详尽分析,本文将帮助开发者有效利用命名空间来优化大型项目结构,确保代码的清晰与高效。 ####
18 1
|
1月前
|
JavaScript
原生js炫酷随机抽奖中奖效果代码
原生js随机抽奖是一个炫酷的根据数据随机抽奖的代码,该网页可进行随机抽取一个数据,页面动画高科技、炫酷感觉的随机抽奖效果,简单好用,欢迎下载!
45 3
原生js炫酷随机抽奖中奖效果代码
|
23天前
|
机器学习/深度学习 存储 人工智能
强化学习与深度强化学习:深入解析与代码实现
本书《强化学习与深度强化学习:深入解析与代码实现》系统地介绍了强化学习的基本概念、经典算法及其在深度学习框架下的应用。从强化学习的基础理论出发,逐步深入到Q学习、SARSA等经典算法,再到DQN、Actor-Critic等深度强化学习方法,结合Python代码示例,帮助读者理解并实践这些先进的算法。书中还探讨了强化学习在无人驾驶、游戏AI等领域的应用及面临的挑战,为读者提供了丰富的理论知识和实战经验。
49 5
|
19天前
|
JSON JavaScript 关系型数据库
node.js连接GBase 8a 数据库 并进行查询代码示例
node.js连接GBase 8a 数据库 并进行查询代码示例
|
1月前
|
JavaScript 前端开发 开发者
如何在 Visual Studio Code (VSCode) 中使用 ESLint 和 Prettier 检查代码规范并自动格式化 Vue.js 代码,包括安装插件、配置 ESLint 和 Prettier 以及 VSCode 设置的具体步骤
随着前端开发技术的快速发展,代码规范和格式化工具变得尤为重要。本文介绍了如何在 Visual Studio Code (VSCode) 中使用 ESLint 和 Prettier 检查代码规范并自动格式化 Vue.js 代码,包括安装插件、配置 ESLint 和 Prettier 以及 VSCode 设置的具体步骤。通过这些工具,可以显著提升编码效率和代码质量。
443 4
|
1月前
|
JavaScript 前端开发 开发者
如何在 Visual Studio Code (VSCode) 中使用 ESLint 和 Prettier 检查代码规范并自动格式化 Vue.js 代码
随着前端开发技术的快速发展,代码规范和格式化工具变得尤为重要。本文介绍如何在 Visual Studio Code (VSCode) 中使用 ESLint 和 Prettier 检查代码规范并自动格式化 Vue.js 代码。通过安装和配置这些工具,可以确保代码风格一致,提高代码质量和可读性。
127 1
|
23天前
|
JavaScript 前端开发 API
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
51 0
|
1月前
|
前端开发 JavaScript
JavaScript新纪元:ES6+特性深度解析与实战应用
【10月更文挑战第29天】本文深入解析ES6+的核心特性,包括箭头函数、模板字符串、解构赋值、Promise、模块化和类等,结合实战应用,展示如何利用这些新特性编写更加高效和优雅的代码。
43 0

推荐镜像

更多