当前位置:首页 > 科技  > 软件

这次,彻底理解 JavaScript 的执行机制

来源: 责编: 时间:2024-06-21 17:25:23 268观看
导读无论你是 JavaScript 的初学者还是专家,无论是为了求职面试还是日常开发工作,我们经常会遇到这样的情况:给出几行代码,我们需要知道它们的输出内容和顺序。由于 JavaScript 是一种单线程语言,我们可以得出以下结论:JavaScri

aVS28资讯网——每日最新资讯28at.com

无论你是 JavaScript 的初学者还是专家,无论是为了求职面试还是日常开发工作,我们经常会遇到这样的情况:给出几行代码,我们需要知道它们的输出内容和顺序。由于 JavaScript 是一种单线程语言,我们可以得出以下结论:aVS28资讯网——每日最新资讯28at.com

JavaScript 按照语句出现的顺序执行。aVS28资讯网——每日最新资讯28at.com

此时,读者可能会说:我知道 JS 是一行一行执行的,为什么还要特别指出呢?冷静下来;正因为 JS 是一行一行执行的,我们假设所有的 JS 都是这样工作的:aVS28资讯网——每日最新资讯28at.com

let a = '1';console.log(a);let b = '2';console.log(b);

然而,实际上 JS 是这样的:aVS28资讯网——每日最新资讯28at.com

setTimeout(function(){  console.log('定时器开始了')});new Promise(function(resolve){  console.log('即将执行for循环');  for(var i = 0; i < 10000; i++){    i == 99 && resolve();  }}).then(function(){  console.log('执行then函数')});console.log('代码执行结束');

遵循 JavaScript 按语句顺序执行的概念,我自信地写下了输出:aVS28资讯网——每日最新资讯28at.com

  • 定时器开始了。
  • 即将执行for循环。
  • 执行then函数。
  • 代码执行结束。

然而,在 Chrome 中验证时,结果完全错误,瞬间迷惑,难道不是按约定的一行一行执行的吗?aVS28资讯网——每日最新资讯28at.com

我们需要彻底理解 JavaScript 的执行机制。aVS28资讯网——每日最新资讯28at.com

关于 JavaScript

JavaScript 是一种单线程语言。尽管在最新的 HTML5 中引入了 Web Worker,但 JavaScript 的单线程核心没有改变。因此,JavaScript 中的所有“多线程”都是使用单线程模拟的,所有的多线程都是欺骗性的!aVS28资讯网——每日最新资讯28at.com

JavaScript 事件循环

由于 JavaScript 是单线程的,就像只有一个窗口的银行,客户需要一个接一个地排队办理业务。同样,JavaScript 任务也需要一个接一个地执行。如果一个任务花费太长时间,那么下一个任务就必须等待。所以问题来了:如果我们想浏览新闻,但新闻中的高清图片加载缓慢,我们的网页是否必须一直卡住,直到图片完全显示?因此,聪明的程序员将任务分为两类:aVS28资讯网——每日最新资讯28at.com

  • 同步任务
  • 异步任务

当我们打开一个网站时,网页的渲染过程由一堆同步任务组成,如渲染页面骨架和页面元素。那些消耗资源多、耗时长的任务,如加载图片或音乐文件,则是异步任务。为了简化理解,我们使用思维导图来说明这一点:aVS28资讯网——每日最新资讯28at.com

aVS28资讯网——每日最新资讯28at.com

如果用文字描述思维导图的内容:aVS28资讯网——每日最新资讯28at.com

  • 同步任务和异步任务进入不同的执行“场所”,同步任务进入主线程,异步任务进入事件表并注册函数。
  • 当指定任务完成时,事件表会将这个函数移动到事件队列中。
  • 主线程中的任务执行完毕后,会从事件队列中读取相应的函数并在主线程中执行。
  • 上述过程会不断重复,这通常被称为事件循环(Event Loop)。

我们不禁要问,如何知道主线程执行栈是否为空?JavaScript 引擎有一个监视过程,持续检查主线程执行栈是否为空。一旦为空,它就会去事件队列中检查是否有等待调用的函数。aVS28资讯网——每日最新资讯28at.com

经过以上描述,一段代码可能会更直观:aVS28资讯网——每日最新资讯28at.com

let data = [];$.ajax({    url: 'www.javascript.com',    data: data,    success: () => {        console.log('发送成功');    }})console.log('代码执行结束');

上面是一段简单的 ajax 请求代码:aVS28资讯网——每日最新资讯28at.com

  • ajax 进入事件表并注册回调函数 success。
  • 执行 console.log('代码执行结束')。
  • ajax 事件完成,回调函数 success 进入事件队列。
  • 主线程从事件队列中读取并执行回调函数 success。

通过以上的文字和代码,相信你对 JavaScript 的执行顺序有了初步的了解。接下来,让我们研究一个高级话题:setTimeoutaVS28资讯网——每日最新资讯28at.com

对 setTimeout 的爱恨情仇

众所周知,setTimeout 无需过多介绍。我们对它的第一印象是它可以在延迟之后异步执行。我们经常使用它来实现 3 秒延迟执行:aVS28资讯网——每日最新资讯28at.com

setTimeout(() => {    task();}, 3000)console.log('执行 console');

随着 setTimeout 的使用逐渐增多,问题也随之而来。有时,即使在代码中指定了 3 秒的延迟,函数也会在 5 或 6 秒后执行。这可能是什么原因造成的呢?aVS28资讯网——每日最新资讯28at.com

我们先看一个例子:aVS28资讯网——每日最新资讯28at.com

setTimeout(() => {    task();}, 3000)console.log('执行 console');

根据我们之前的结论,setTimeout 是异步的,所以同步任务 console.log 应该先执行。因此,我们的结论是:aVS28资讯网——每日最新资讯28at.com

  • 执行 console
  • task()

为了验证,结果是正确的!然后让我们对之前的代码做一些修改:aVS28资讯网——每日最新资讯28at.com

setTimeout(() => {    task();}, 3000)sleep(10000000)

乍一看,这似乎类似,但当我们在 Chrome 中执行这段代码时,发现 console 的执行时间远远超过 3 秒。为什么现在需要这么长时间呢?aVS28资讯网——每日最新资讯28at.com

此时,我们需要重新定义 setTimeout。让我们来讨论上面代码的执行过程:aVS28资讯网——每日最新资讯28at.com

  • task() 进入事件表并注册,计时开始。
  • 执行非常缓慢的 sleep 函数,计时继续。
  • 3 秒钟过去,计时事件 timeout 完成。task() 进入事件队列。但是,sleep 太慢,还没有执行完毕;所以我们必须等待。
  • 最后,sleep 执行完毕。task() 终于从事件队列移动到主线程执行。

经过上述过程,我们了解到 setTimeout 函数会在指定时间后将任务(在这个例子中是 task())添加到事件队列中。由于任务在单线程环境中一个接一个地执行,如果前面的任务执行时间过长,执行时间将显著超过 3 秒。aVS28资讯网——每日最新资讯28at.com

我们经常遇到类似 setTimeout(fn, 0) 的代码。0 秒后执行意味着什么?它能立即执行吗?aVS28资讯网——每日最新资讯28at.com

答案是否定的。setTimeout(fn, 0) 的意思是指定某个任务在主线程最早的空闲时间执行,不需要等待任何额外的秒数,一旦所有同步任务在栈中完成并且栈变为空。例如:aVS28资讯网——每日最新资讯28at.com

// 代码 1console.log('先执行这里');setTimeout(() => {    console.log('执行了')}, 0);// 代码 2console.log('先执行这里');setTimeout(() => {    console.log('执行了')}, 3000);

代码 1 的输出结果是:aVS28资讯网——每日最新资讯28at.com

  • 先执行这里
  • 执行了

代码 2 的输出结果是:aVS28资讯网——每日最新资讯28at.com

  • 先执行这里
  • ... 3 秒后
  • 执行了

关于 setTimeout 需要注意的是,即使主线程空闲,0 毫秒也无法实现。根据 HTML 标准,最小值为 4 毫秒。感兴趣的同学可以自行探索。aVS28资讯网——每日最新资讯28at.com

双胞胎兄弟 setInterval

谈到 setTimeout,我们不能错过它的双胞胎兄弟 setInterval。它们很相似,只不过后者是循环执行的。从执行顺序来看,setInterval 会在每个指定的间隔时间将注册的函数放入事件队列。如果前一个任务花费太长时间,它也需要等待。aVS28资讯网——每日最新资讯28at.com

唯一需要注意的是,对于 setInterval(fn, ms),我们已经知道 fn 不会每 ms 秒执行一次,而是在每 ms 秒将一个新的 fn 实例放入事件队列。如果 setInterval 的回调函数(fn)花费的时间超过了延迟时间(ms),那么将不会有明显的时间间隔。请仔细思考这句话。aVS28资讯网——每日最新资讯28at.com

Promise 和 process.nextTick(callback)

我们已经研究了传统的定时器,接下来,我们将探索 Promise 和 process.nextTick(callback) 的表现。aVS28资讯网——每日最新资讯28at.com

Promise 的定义和功能在本文中不会详细展开。而 process.nextTick(callback) 类似于 Node.js 中的 “setTimeout”,在事件循环的下一轮调用回调函数。aVS28资讯网——每日最新资讯28at.com

切入正题,除了同步任务和异步任务的广义定义外,我们还有更精细的任务定义:aVS28资讯网——每日最新资讯28at.com

  • 宏任务(macro-task):包括整体代码、setTimeout、setInterval
  • 微任务(micro-task):Promise、process.nextTick

不同类型的任务将进入相应的事件队列;例如,setTimeout 和 setInterval 将进入同一个事件队列。aVS28资讯网——每日最新资讯28at.com

事件循环中的事件顺序决定了 JavaScript 代码的执行顺序。在进入整体代码(宏任务)后,它开始其第一次循环。然后,它执行所有的微任务。接下来,它再次从宏任务开始,直到一个任务队列完成,再次执行所有的微任务。听起来有点复杂;让我们用本文前面的一个代码片段来说明:aVS28资讯网——每日最新资讯28at.com

setTimeout(function() {    console.log('setTimeout');})new Promise(function(resolve) {    console.log('promise');}).then(function() {    console.log('then');})console.log('console');
  • 这段代码作为宏任务进入主线程。
  • 遇到 setTimeout,它的回调函数被注册并分派到宏任务事件队列中。
  • 接下来,遇到 Promise,new Promise 立即执行,并将 then 函数分派到微任务事件队列中。
  • 遇到 console.log(),立即执行。
  • 在作为第一个宏任务执行整体代码后,我们看看有哪些微任务。我们发现 then 在微任务事件队列中,并执行它。
  • 事件循环的第一轮结束。让我们从宏任务事件队列开始第二轮循环。我们发现这个队列中对应于 setTimeout 的回调函数立即执行。
  • 结束

事件循环、宏任务和微任务之间的关系如图所示:aVS28资讯网——每日最新资讯28at.com

aVS28资讯网——每日最新资讯28at.com

我们分析一段更复杂的代码,看看您是否理解了 JavaScript 的执行机制:aVS28资讯网——每日最新资讯28at.com

console.log('1');setTimeout(function() {    console.log('2');    process.nextTick(function() {        console.log('3');    })    new Promise(function(resolve) {        console.log('4');        resolve();    }).then(function() {        console.log('5')    })})process.nextTick(function() {    console.log('6');})new Promise(function(resolve) {    console.log('7');    resolve();}).then(function() {    console.log('8')})setTimeout(function() {    console.log('9');    process.nextTick(function() {        console.log('10');    })    new Promise(function(resolve) {        console.log('11');        resolve();    }).then(function() {        console.log('12')    })})

事件循环第一轮过程分析如下:aVS28资讯网——每日最新资讯28at.com

  • 整体代码作为第一个宏任务进入主线程,遇到 console.log 并输出 1。
  • 遇到 setTimeout,它的回调函数被分派到宏任务事件队列中,我们暂时称之为 setTimeout1。
  • 遇到 process.nextTick(),它的回调函数被分派到微任务事件队列中,我们称之为 process1。
  • 遇到 Promise,new Promise 直接执行并输出 7,then 方法分派到微任务事件队列中,我们称之为 then1。
  • 再次遇到 setTimeout,它的回调函数被分派到宏任务事件队列中,我们称之为 setTimeout2。

aVS28资讯网——每日最新资讯28at.com

  • 在事件循环宏任务第一轮结束时,输出 1 和 7。
  • 我们发现两个微任务:process1 和 then1。
  • 执行 process1 输出 6。
  • 执行 then1 输出 8。

第一轮事件循环正式结束,结果输出为 1, 7, 6, 8。第二轮事件循环从 setTimeout1 宏任务开始:aVS28资讯网——每日最新资讯28at.com

  • 首先,输出 2。接下来,遇到 process.nextTick(),将其分派到微任务事件队列中,标记为 process2。new Promise 立即执行并输出 4,然后分派到微任务事件队列中,标记为 then2。

aVS28资讯网——每日最新资讯28at.com

  • 在第二轮事件循环宏任务结束后,我们发现有两个微任务,process2 和 then2,可以执行。
  • 输出 3。
  • 输出 5。
  • 第二轮事件循环结束,输出为 2, 4, 3, 5。
  • 第三轮事件循环开始,此时只有 setTimeout2 剩下等待执行。
  • 直接输出 9。
  • 分派 process.nextTick() 到微任务事件队列,标记为 process3。
  • new Promise 直接执行并输出 11。
  • 分派 then 到微任务事件队列,标记为 then3。

aVS28资讯网——每日最新资讯28at.com

  • 第三轮事件循环宏任务执行完成,执行两个微任务 process3 和 then3。
  • 输出 10。
  • 输出 12。
  • 第三轮事件循环结束,输出为 9, 11, 10, 12。

整个代码段经过了三轮事件循环,完整输出为 1, 7, 6, 8, 2, 4, 3, 5, 9, 11, 10, 12。aVS28资讯网——每日最新资讯28at.com

在 Node 环境中的事件监听依赖于 libuv,与前端环境不完全相同,输出顺序可能会有差异。aVS28资讯网——每日最新资讯28at.com

总结

JavaScript 的异步性:从一开始,我们就说过 JavaScript 是单线程语言。无论使用什么新框架或语法糖来实现所谓的异步性,都是通过同步方法模拟的。牢牢把握单线程这一点非常重要。aVS28资讯网——每日最新资讯28at.com

事件循环:事件循环是 JavaScript 实现异步操作的方法,也是其执行机制。aVS28资讯网——每日最新资讯28at.com

JavaScript 的执行与运行:执行和运行有很大区别。JavaScript 的执行方式在不同环境中有所不同,如 Node.js、浏览器、Ringo 等。然而,运行大多指 JavaScript 解析引擎,保持一致。aVS28资讯网——每日最新资讯28at.com

setImmediate:有许多类型的微任务和宏任务,如 setImmediate 等,它们的执行有共同点。感兴趣的同学可以自行探索。aVS28资讯网——每日最新资讯28at.com

最后但同样重要的是:JavaScript 是单线程语言,事件循环是其执行机制。 牢牢掌握这两个基本点,认真学习 JavaScript,很快实现成为优秀前端开发者的伟大梦想!aVS28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-95556-0.html这次,彻底理解 JavaScript 的执行机制

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 如何更改 .NET 中的默认时区?

下一篇: 接口性能优化的11个小技巧

标签:
  • 热门焦点
  • K60至尊版刚预热 一加Ace2 Pro正面硬刚

    Redmi这边刚如火如荼的宣传了K60 Ultra的各种技术和硬件配置,作为竞品的一加也坐不住了。一加中国区总裁李杰发布了两条微博,表示在自家的一加Ace2上早就已经采用了和PixelWo
  • 影音体验是真的强 简单聊聊iQOO Pad

    大公司的好处就是产品线丰富,非常细分化的东西也能给你做出来,例如早先我们看到了新的vivo Pad2,之后我们又在iQOO Neo8 Pro的发布会上看到了iQOO的首款平板产品iQOO Pad。虽
  • Raft算法:保障分布式系统共识的稳健之道

    1. 什么是Raft算法?Raft 是英文”Reliable、Replicated、Redundant、And Fault-Tolerant”(“可靠、可复制、可冗余、可容错”)的首字母缩写。Raft算法是一种用于在分布式系统
  • K8S | Service服务发现

    一、背景在微服务架构中,这里以开发环境「Dev」为基础来描述,在K8S集群中通常会开放:路由网关、注册中心、配置中心等相关服务,可以被集群外部访问;图片对于测试「Tes」环境或者
  • 为什么你不应该使用Div作为可点击元素

    按钮是为任何网络应用程序提供交互性的最常见方式。但我们经常倾向于使用其他HTML元素,如 div span 等作为 clickable 元素。但通过这样做,我们错过了许多内置浏览器的功能。
  • 2023年,我眼中的字节跳动

    此时此刻(2023年7月),字节跳动从未上市,也从未公布过任何官方的上市计划;但是这并不妨碍它成为中国最受关注的互联网公司之一。从2016-17年的抖音强势崛起,到2018年的&ldquo;头腾
  • 华为Mate 60系列用上可变灵动岛:正式版体验将会更出色

    这段时间以来,关于华为新旗舰的爆料日渐密集。据此前多方爆料,今年华为将开始恢复一年双旗舰战略,除上半年推出的P60系列外,往年下半年的Mate系列也将
  • iQOO Neo8系列或定档5月23日:首发天玑9200+ 安卓跑分王者

    去年10月,iQOO推出了iQOO Neo7系列机型,不仅搭载了天玑9000+,而且是同价位唯一一款天玑9000+直屏旗舰,一经上市便受到了用户的广泛关注。在时隔半年后,
  • OPPO K11样张首曝:千元机影像“卷”得真不错!

    一直以来,OPPO K系列机型都保持着较为均衡的产品体验,历来都是2K价位的明星机型,去年推出的OPPO K10和OPPO K10 Pro两款机型凭借各自的出色配置,堪称有
Top