前几天在一次技术面试中,被问到的一个问题是“如何优化 Javascript 代码”?
令我惊讶的是,他告诉我 while 循环通常比 for 循环快。
这是真的吗?如果是,那是为什么?
前几天在一次技术面试中,被问到的一个问题是“如何优化 Javascript 代码”?
令我惊讶的是,他告诉我 while 循环通常比 for 循环快。
这是真的吗?如果是,那是为什么?
你应该反驳说负while
循环会更快!请参阅:JavaScript 循环性能 - 为什么将迭代器递减到 0 比递增快。
在while
vsfor
中,这两个来源通过在不同的浏览器中运行各种循环并以毫秒为单位比较结果,很好地记录了速度现象:
https ://blogs.oracle.com/greimer/entry/best_way_to_code_a和:
http://www.stoimen .com/blog/2012/01/24/javascript-performance-for-vs-while/。
从概念上讲,for
循环基本上是一个封装while
循环,专门用于递增或递减(根据某种顺序或某种长度在逻辑上进行)。例如,
for (let k = 0; ++k; k < 20) {…}
可以通过使其成为负 while 循环来加速:
var k = 20;
while (--k) {…}
正如您从上面链接中的测量结果中看到的那样,节省的时间确实加起来非常大。
虽然这是对速度和效率的分钟检测的一个很好的答案,但我不得不离题到@Pointy 原始声明。
正确的答案是担心这些细节通常是没有意义的,因为你在这些优化上所做的任何努力都可能在下次签入 V8 或 SpiderMonkey 时完全浪费
由于 Javascript 是客户端确定的,并且最初必须针对每个浏览器进行编码以实现完全的跨浏览器兼容性(在 ECMA 甚至涉及之前,情况更糟),由于显着优化,此时速度差异甚至可能不是一个合乎逻辑的答案以及在浏览器及其编译器引擎上采用 Javascript。
我们甚至没有谈论非严格的脚本编写,例如 GAS 中的应用程序,所以虽然答案和问题很有趣,但它们在现实世界的应用程序中很可能是微不足道的而不是有用的。
要阐述这个主题,您首先需要了解这个主题的最初来源以及编译与解释。让我们简要回顾一下语言的演变历史,然后再回到编译与解释。虽然不是必需阅读,但您可以阅读 Compiling vs Interpeting 以获得快速答案,但为了深入理解,我建议您阅读 Compiling vs Interpreting 和 the Evolution of Programming(展示它们今天的应用方式)。
编译语言编码是一种编程方法,在这种方法中,您以编译器可以理解的可编译方式编写代码,当今一些更受认可的语言是 Java、C++ 和 C#。编写这些语言的目的是让编译器程序将代码翻译成目标机器使用的机器码或字节码。
解释代码
是刚刚处理的代码ime (JIT) 在执行时没有先编译,它会跳过这一步,并允许更快的编写、调试、添加/更改等。它也永远不会存储脚本的解释以供将来使用,它会重新解释每次调用方法时的脚本。解释的代码在定义的和预期的程序运行时环境(对于 javascript 通常是浏览器)中运行,一旦被环境解释,就会输出到所需的结果。解释脚本绝不意味着是独立软件,并且总是希望插入有效的运行时环境以进行解释。这就是脚本不可执行的原因。他们永远不会直接与操作系统通信。如果您查看正在发生的系统进程,您将永远不会看到正在处理您的脚本,
因此,用 Javascript 编写 hello 脚本意味着浏览器会解释代码,定义 hello 是什么,当发生这种情况时,浏览器会将这段代码翻译回机器级代码,说我有这个脚本并且我的环境想要显示单词 hello 所以然后机器将其处理成脚本的可视化表示。这是一个恒定的过程,这就是为什么您在计算机中拥有处理器以及在系统上发生的恒定处理动作的原因。没有什么是静止的,无论情况如何,流程都在不断地执行。
编译器
通常将代码编译成定义的字节码系统或机器代码语言,现在是代码的静态版本。除非重新编译源代码,否则机器不会重新解释它。这就是为什么您会在编译后看到运行时错误,然后程序员必须在源代码中调试并重新编译。解释器预期的脚本(如 Javascript 或 PHP)只是在运行之前未编译的指令,因此源代码很容易编辑和修复,而无需额外的编译步骤,因为编译是实时完成的。
并非所有编译代码都是平等
的 一个简单的说明方法是视频游戏系统。Playstation 与 Xbox。Xbox 系统是为支持 .net 框架而构建的,以优化编码和开发。C# 将此框架与公共语言运行时结合使用,以便将代码编译为字节码。字节码不是对已编译代码的严格定义,它是过程中的一个中间步骤,它允许更快地和更大规模地编写程序代码,然后在运行时使用代码执行时解释它,你猜对了,准时( JIT )。_ 不同之处在于该代码仅被解释一次,一旦编译,程序将不会再次重新解释该代码,除非重新启动。
解释脚本语言永远不会编译代码,因此解释脚本中的函数不断被重新处理,而编译的字节码的函数被解释一次并且指令被存储,直到程序的运行时停止。好处是字节码可以移植到另一台机器的架构中,前提是您拥有必要的资源。这就是为什么您必须在系统中安装 .net 以及可能的更新和框架,以使程序正常工作。
Playstation 不为其机器使用 .net 框架。您将需要使用 C++ 编写代码,C++ 旨在针对特定系统架构进行编译和组装。代码永远不会被解释,并且需要完全正确才能运行。您永远无法像移动中间语言那样轻松移动这种类型的语言。它是专门为该机器的架构而设计的,永远不会被解释为其他方式。
所以你会看到,即使是编译语言本身也不是编译语言的最终版本。编译语言在其严格定义中意味着完全编译以供使用。解释性语言旨在由程序解释,但也是编程中最可移植的语言,因为只需要安装一个理解脚本的程序,但由于不断被解释,它们也使用最多的资源。中间语言(如 Java 和 C#)是这两种语言的混合体,部分编译但也需要外部资源才能正常运行。一旦运行,它们就会再次编译,这是在运行时的一次性解释。
机器代码 编码
的最低形式,此代码的表示形式是严格的二进制(我不会进入三进制计算,因为它基于此讨论的理论和实际应用)。计算机理解自然值,开/关真/假。这是机器级别的数字代码,与下一级汇编代码不同。
汇编代码
直接下一级代码是汇编语言。这是将语言解释为由机器使用的第一点。此代码旨在解释助记符、符号和操作数,然后以机器级代码发送到机器。理解这一点很重要,因为当您第一次开始编程时,大多数人都会假设它是这个或那个意味着我编译或解释。除了低级机器代码之外,没有任何编码语言是仅编译指令或仅解释指令!!!
我们在“并非所有编译的代码都是平等的”中讨论了这一点。汇编语言是第一个例子。机器代码是机器可以读取的,而汇编语言是人类可以读取的。随着计算机处理速度更快,通过更好的技术进步,我们的低级语言在本质上开始变得更加简洁,不需要手动实现。汇编语言曾经是高级编码语言,因为它是对机器进行编码的更快方法。它本质上是一种语法语言,一旦汇编(编译的最低形式)直接转换为机器语言。汇编器是编译器,但并非所有编译器都是汇编器。
高级编码
高级编码语言是比汇编高一级但甚至可能包含更高级别的语言(这将是字节码/中间语言)。这些语言从那里定义的语法结构编译成所需的机器代码、要解释的字节码或前一种方法与允许内联编写汇编的特殊编译器相结合的混合体。像它的前身 Assembly 一样的高级编码旨在减少开发人员的工作量并消除冗余任务(如构建可执行程序)中出现严重错误的任何机会。在当今世界中,您很少会看到开发人员在汇编中工作只是为了获得大小的好处而处理数据。通常情况下,开发人员可能会遇到这样的情况,例如在视频游戏机开发中,他们需要在此过程中提高速度。因为高级编码编译器是寻求简化开发过程的工具,所以它们可能无法 100% 地以最有效的方式编译代码以用于该系统架构。在这种情况下,将编写汇编代码以最大限度地利用系统资源。但是你永远不会看到有人用机器码写东西,除非你遇到一个怪人。
如果你做到了这一步,恭喜!你只是坐下来听了比我妻子听得更多的关于这件事的事,一辈子。OP 的问题是关于 while 与 for 循环的性能。在今天的标准中,这是一个有争议的问题的原因有两个。
原因一
解释 Javascript 的日子已经一去不复返了。所有主要浏览器(是的,甚至 Opera 和 Netscape)都使用 Javascript 引擎,该引擎用于在执行脚本之前对其进行编译。JS 开发人员在查看语言中的本机函数时,就非调用方法讨论的性能调整是过时的研究方法。在成为 DOM 的一部分之前,代码已经为此进行了编译和优化。该页面启动时不会再次解释它,因为该页面是运行时环境。Javascript 已经真正成为一种中间语言,而不是解释脚本。它永远不会被称为中间脚本语言的原因是因为 Javascript 永远不会被编译。这是唯一的原因。除此之外它'
原因二 您编写脚本或脚本库的机会几乎为零,因为它需要与网站上的桌面应用程序一样多的处理能力。为什么?因为 Javascript 的创建从未打算成为一种包罗万象的语言。它的创建只是为了提供一种中级语言编程方法,允许完成 HTML 和 CSS 不提供的流程,同时缓解需要专用高级编码语言,特别是 Java 的开发困难。
早期 Web 开发的大部分时间都不支持 CSS 和 JS。直到 1997 年左右,CSS 还不是一个安全的集成,而 JS 的战斗时间更长。除了 HTML 之外的所有东西都是网络世界中的一种补充语言。
HTML 专门用于作为站点的构建块。您永远不会编写 javascript 来完全构建网站。最多你会做 DOM 操作,但要建立一个站点。
您永远不会在 JS 中为您的网站设置样式,因为它不实用。CSS 处理该过程。
除了暂时的,你永远不会使用 Javascript 存储。你会使用数据库。
那么我们还剩下什么?越来越只是功能和流程。CSS3 及其未来的迭代将采用 Javascript 的所有样式方法。您已经通过动画和伪状态(悬停、活动等)看到了这一点。
在这一点上,在 Javascript 中优化代码的唯一有效论据是编写糟糕的函数、方法和操作,这些函数、方法和操作可以通过优化用户的公式/代码模式得到帮助。只要您学习正确且有效的编码模式,Javascript 在当今时代就不会损失其本机功能的性能。
for(var k=0; ++k; k< 20){ ... }
可以通过使其成为负 while 循环来加速:
var k = 20; while(--k){ ... };
更准确的测试是使用for到与while相同的程度。唯一的区别是使用 for 循环提供了更多描述。如果我们想变得超级疯狂,我们可以放弃整个街区;
var k = 0;
for(;;){doStuff till break}
//or we could do everything
for (var i=1, d=i*2, f=Math.pow(d, i); f < 1E9; i++, d=i*2, f=Math.pow(d,i)){console.log(f)}
无论哪种方式...在 NodeJS v0.10.38 中,我在四分之一秒内处理了一个 10 9的 JavaScript 循环,平均速度提高了 13%。但这确实对我未来使用哪个循环的决定或我选择在循环中描述的数量没有影响。
> t=Date.now();i=1E9;
> while(i){--i;b=i+1}console.log(Date.now()-t);
292
> t=Date.now();i=1E9;
> while(--i){b=i+1}console.log(Date.now()-t);
285
> t=Date.now();i=1E9;
> for(;i>0;--i){b=i+1}console.log(Date.now()-t);
265
> t=Date.now();i=1E9;
> for(;i>0;){--i;b=i+1}console.log(Date.now()-t);
246
2017年答案
Chrome 59 上的 jsperf for vs foreach
在这里,您可以看到Array.forEach
截至撰写日期 (7/31/17) 在最新版本的 Chrome (59) 上速度最快。您可以在此处找到其他浏览器版本的平均时间:https ://jsperf.com/for-vs-foreach/66 。
这证明了ES引擎优化随时改变什么是更有效的。
我的建议是您使用对您的用例更具表现力的那个。
随着计算机按照摩尔定律呈指数级增长,相同数量级的性能差异在未来将几乎无关紧要。
在 JavaScript 中,反向 for 循环是最快的。For 循环比while 循环快得多。更加注重可读性。
测试了以下循环:
var i,
len = 100000,
lenRev = len - 1;
i = 0;
while (i < len) {
1 + 1;
i += 1;
}
i = lenRev;
while (-1 < i) {
1 + 1;
i -= 1;
}
for (i = 0; i < len; i += 1) {
1 + 1;
}
for (i = lenRev; - 1 < i; i -= 1) {
1 + 1;
}