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

简介: 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

目录
相关文章
|
6天前
|
JavaScript 前端开发 开发者
Nest.js控制器深度解析:路由与请求处理的高级特性
以上就是对 NestJS 控制层高级特性深度解析:从基本概念到异步支持再到更复杂场景下拦截其与管道等功能性组件运用都有所涉及,希望能够帮助开发者更好地理解和运用 NestJS 进行高效开发工作。
211 15
|
17天前
|
JavaScript 前端开发 IDE
TypeScript vs. JavaScript:技术对比与核心差异解析
TypeScript 作为 JavaScript 的超集,通过静态类型系统、编译时错误检测和强大的工具链支持,显著提升代码质量与可维护性,尤其适用于中大型项目和团队协作。相较之下,JavaScript 更灵活,适合快速原型开发。本文从类型系统、错误检测、工具支持等多维度对比两者差异,并提供技术选型建议,助力开发者合理选择。
207 1
|
1月前
|
存储 JavaScript 前端开发
JavaScript 语法全面解析
JavaScript 语法体系丰富且不断更新,从基础的变量声明、数据类型,到复杂的函数、对象、异步语法,每个知识点都需要开发者深入理解并灵活运用。本文梳理的 JS 语法核心内容,可为开发者提供系统的学习框架,后续还需通过大量实践(如编写交互组件、实现业务逻辑)巩固知识,逐步提升 JS 编程能力,应对前端开发中的各类挑战。
|
17天前
|
JavaScript 前端开发 安全
【逆向】Python 调用 JS 代码实战:使用 pyexecjs 与 Node.js 无缝衔接
本文介绍了如何使用 Python 的轻量级库 `pyexecjs` 调用 JavaScript 代码,并结合 Node.js 实现完整的执行流程。内容涵盖环境搭建、基本使用、常见问题解决方案及爬虫逆向分析中的实战技巧,帮助开发者在 Python 中高效处理 JS 逻辑。
|
3月前
|
JavaScript 前端开发 算法
流量分发代码实战|学会用JS控制用户访问路径
流量分发工具(Traffic Distributor),又称跳转器或负载均衡器,可通过JavaScript按预设规则将用户随机引导至不同网站,适用于SEO优化、广告投放、A/B测试等场景。本文分享一段不到百行的JS代码,实现智能、隐蔽的流量控制,并附完整示例与算法解析。
94 1
|
4月前
|
JavaScript 前端开发
怀孕b超单子在线制作,p图一键生成怀孕,JS代码装逼娱乐
模拟B超单的视觉效果,包含随机生成的胎儿图像、医疗文本信息和医院标志。请注意这仅用于前端开发学习
|
4月前
|
机器学习/深度学习 JavaScript 前端开发
JS进阶教程:递归函数原理与篇例解析
通过对这些代码示例的学习,我们已经了解了递归的原理以及递归在JS中的应用方法。递归虽然有着理论升华,但弄清它的核心思想并不难。举个随手可见的例子,火影鸣人做的影分身,你看到的都是同一个鸣人,但他们的行为却能在全局产生影响,这不就是递归吗?雾里看花,透过其间你或许已经深入了递归的魅力之中。
155 19
|
5月前
|
JSON 前端开发 Serverless
Mock.js 语法结构全解析
Mock.js 的语法规范介绍,从数据模板定义规范和数据占位符定义规范俩部分介绍, 让你更好的使用 Mock.js 来模拟数据并提高开发效率。
|
4月前
|
JavaScript
JS代码的一些常用优化写法
JS代码的一些常用优化写法
72 0
|
6月前
|
存储 JavaScript 前端开发
在NodeJS中使用npm包进行JS代码的混淆加密
总的来说,使用“javascript-obfuscator”包可以帮助我们在Node.js中轻松地混淆JavaScript代码。通过合理的配置,我们可以使混淆后的代码更难以理解,从而提高代码的保密性。
456 9

推荐镜像

更多
  • DNS