影响JavaScript性能的另外一个杀手就是递归,在上一节中提到采用memoization技术可以优化计算数值的递归函数,但
memoization不是万能的,不是所有的递归函数都可以用memoization技术优化,本文介绍了这些情况,并介绍了解决办法,就是将递归转换
为迭代,同时需要注意,本文末尾介绍的方案不是最终的方案,还需要和上一节优化循环的方案综合起来才能达到最佳效果。
【原文】Speed up your JavaScript, Part 3
【作者】Nicholas C. Zakas
【译文】http://cuimingda.com/2009/02/speed-up-your-javascript-part-3.html
【译者】明达
以下是对原文的翻译
:
递归是拖慢脚本运行速度的大敌之一。太多的递归会让浏览器变得越来越慢直到死掉或者莫名其妙的突然自动退出,所以我们一定要解决在
JavaScript中出现的这一系列性能问题。在这个系列文章的第二篇中,我曾经简短的介绍了如何通过memoization技术来替代函数中太多的递
归调用。memoization是一种可以缓存之前运算结果的技术,这样我们就不需要重新计算那些已经计算过的结果。对于通过递归来进行计算的函
数,memoization简直是太有用了。我现在使用的memoizer是由
Crockford写的,主要应用在那些返回整数的递归运算中。当然并不是所有的递归函数都返回整数,所以我们需要一个更加通用的memoizer()函
数来处理更多类型的递归函数。
function memoizer(fundamental, cache) {
cache = cache || {};
var shell = function(arg) {
if (! (arg in cache)) {
cache[arg] = fundamental(shell, arg);
}
return cache[arg];
};
return shell;}
这个版本的函数和Crockford写的版本有一点点不同。首先,参数的顺序被颠倒了,原有函数被设置为第一个参数,第二个参数是缓存对象,为可选
参数,因为并不是所有的递归函数都包含初始信息。在函数内部,我将缓存对象的类型从数组转换为对象,这样这个版本就可以适应那些不是返回整数的递归函数。
在shell函数里,我使用了in操作符来判断参数是否已经包含在缓存里。这种写法比测试类型不是undefined更加安全,因为undefined是
一个有效的返回值。我们还是用之前提到的斐波纳契数列来做说明:
var fibonacci = memoizer(function(recur, n) {
return recur(n - 1) + recur(n - 2);
}, { "0": 0, "1": 1} );
同样的,执行fibonacci(40)这个函数,只会对原有的函数调用40次,而不是夸张的331,160,280次。memoization对
于那些有着严格定义的结果集的递归算法来说,简直是棒极了。然而,确实还有很多递归算法不适合使用memoization方法来进行优化。
我在学校时的一位教授一直坚持认为,任何使用递归的情况,如果有需要,都可以使用迭代来代替。实际上,递归和迭代经常会被作为互相弥补的方法,尤其
是在另外一种出问题的情况下。将递归算法转换为迭代算法的技术,也是和开发语言无关的。这对JavaScript来说是很重要的,因为很多东西在执行环境
中是受到限制的(the importance in JavaScript is greater, though, because the
resources of the execution environment are so
restrictive.)。让我们回顾一个典型的递归算法,比如说归并排序,在JavaScript中实现这个算法需要下面的代码:
function merge(left, right) {
var result = [];
while (left.length > 0 && right.length > 0) {
if (left[0] < right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
return result.concat(left).concat(right);
}
//采用递归实现的归并排序算法
function mergeSort(items) {
if (items.length == 1) {
return items;
}
var middle = Math.floor(items.length / 2),
left = items.slice(0, middle),
right = items.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
调用mergeSort()函数处理一个数组,就可以返回经过排序的数组。注意每次调用mergeSort()函数,都会有两次递归调用。这个算法
不可以使用memoization来进行优化,因为每个结果都只计算并使用一次,就算缓冲了结果也没有什么用。如果你使用mergeSort()函数来处
理一个包含100个元素的数组,总共会有199次调用。1000个元素的数组将会执行1999次调用。在这种情况下,我们的解决方案是将递归算法转换为迭
代算法,也就是说要引入一些循环(关于算法,可以参考这篇《List Processing: Sort Again, Naturally
》):
// 采用迭代实现的归并排序算法
function mergeSort(items) {
if (items.length == 1) {
return items;
}
var work = [];
for (var i = 0,
len = items.length; i < len; i++) {
work.push([items[i]]);
}
work.push([]); //in case of odd number of items
for (var lim = len; lim > 1; lim = (lim + 1) / 2) {
for (var j = 0,
k = 0; k < lim; j++, k += 2) {
work[j] = merge(work[k], work[k + 1]);
}
work[j] = []; //in case of odd number of items
}
return work[0];
}
这个归并排序算法实现使用了一系列循环来代替递归进行排序。由于归并排序首先要将数组拆分成若干只有一个元素的数组,这个方法更加明确的执行了这个
操作,而不是通过递归函数隐晦的完成。work数组被初始化为包含一堆只有一个元素数组的数组。在循环中每次会合并两个数组,并将合并后的结果放回
work数组中。当函数执行完成后,排序的结果会通过work数组中的第一个元素返回。在这个归并排序的实现中,没有使用任何递归,同样也实现了这个算
法。然而,这样做却引入了大量的循环,循环的次数基于要排序的数组中元素的个数,所以我们可能需要使用在 上篇讨论过的技术
来进行修订,处理这些额外开销。
总结一下基本原则,不管是什么时候使用递归的时候都应该小心谨慎。memoization和迭代是代替递归的两种解决方案,最直接的结果当然就是避免那个 提示脚本失控的对话框
。
相关推荐
JavaScript 运行速度优化(递归篇) 本文主要介绍了如何提升 JavaScript 的运行速度,特别是针对递归算法的优化方法。递归是 JavaScript 中的一个性能杀手,它可以让浏览器变得越来越慢,直到死掉或者莫名其妙的...
如何提升JavaScript的运行速度(函数篇) 本文译自 Nicholas C. Zakas 于 2009 年 1 月 20 日在个人网站上发表的《Speed up your JavaScript, Part 2》。本文主要讨论了如何重构嵌套循环、递归,以及那些在函数内部...
总之,提升JavaScript的运行速度需要从多个方面进行考虑,包括循环结构的优化、异步处理、减少DOM操作、使用内置函数和优化数据结构等。通过这些策略,可以显著提高代码执行效率,改善用户体验,同时避免浏览器因...
为了提升JavaScript程序的运行速度,特别是针对循环操作的优化,我们有必要了解那些会拖慢JavaScript脚本运行的不良代码实践,并了解如何使用现代JavaScript技术来避免这些问题。 首先,我们来分析一下Nicholas C. ...
在深入探讨JavaScript中递归、迭代和查表法的应用之前,先来明确这些概念。递归是一种编程技术,函数通过直接或间接调用自身来解决一个问题,常见的应用场景包括排序算法(如快速排序)、数据处理(如树遍历)等。而...
在实际项目中,合理运用这些算法,可以使代码更加简洁、高效,提高程序的运行速度和用户体验。 总之,"Algorithm-javascript-algorithms.zip"提供了一个丰富的学习资源,涵盖了计算机科学中的核心算法。无论是初学...
在JavaScript中,递归函数是一种常见的编程范式,其中函数调用自身来解决问题。然而,在处理复杂或者大量...它特别适合于计算状态不变、计算量大的问题,通过缓存中间结果来避免重复计算,从而加快整个程序的运行速度。
- **算法优化**:理解并实现高效算法以提高程序运行速度。 - **流程控制**:使用条件语句、循环等语法结构来控制程序的执行流程。 **技术细节:** - 在JavaScript中,合理的算法设计可以显著提高程序性能。 - 循环...
JavaScript是一种广泛应用于网页和网络应用开发的编程语言,它提供了丰富的功能来增强用户界面和交互性。...通过不断学习和实践,开发者可以利用JavaScript创造出更多富有创意和实用性的效果,提升网站的用户体验。
JavaScript是一种广泛应用于网页和应用程序的脚本语言,它在客户端运行,无需服务器支持即可实现动态交互功能。在网页设计中,JavaScript特效为用户提供更丰富的视觉体验,其中字符滚动隐现效果是常见的一种,用于...
- 性能优化:减少不必要的计算,提高游戏运行速度。 通过学习和理解这个JavaScript数独游戏的实现,开发者不仅可以提升JavaScript技能,还能深入理解游戏逻辑和算法应用,这对于开发其他类型的互动应用也非常有...
例如,理解栈和队列可以优化异步处理,掌握哈希表则有助于提高查找效率,而各种排序算法则直接影响到程序的运行速度。 在ljg_resource1这个文件中,可能包含了详细的教程、示例代码以及练习题,帮助读者深入理解并...
JavaScript是一种广泛使用的客户端脚本语言,它可以直接在用户的浏览器上运行,为网页添加交互性和动态功能。 在【标签】"Javascript"中,我们确认了这个特效是通过JavaScript编程实现的。JavaScript可以操作DOM...
JS-Profiler包含对递归测试的支持,这使得开发者能够测试在大量数据上运行的递归算法的性能,检查是否存在栈溢出或过慢的运行速度等问题。 3. **性能指标**:JS-Profiler可能会提供诸如平均执行时间、最大执行时间...
- **9.4 性能优化**:提供一些建议,帮助开发者提高 JavaScript 代码的运行效率。 - **9.5 异步编程模式**:讨论异步编程的各种模式,如回调、Promise、async/await 等。 #### 10. JavaScript 工程化 - **10.1 ...
在JavaScript开发领域,性能优化是提升应用运行效率的关键环节。为了达到这一目标,开发者经常使用各种工具和技术来优化代码。其中,“faster.js”是一个值得关注的Babel插件,它专为将惯用的JavaScript转换为更快、...
这大大减少了搜索空间,提高了算法的运行速度。 **在竞争性搜索中的应用** 在“competitive_search-master”这样的项目中,minimax算法可能被用于解决某种形式的搜索问题,例如在棋类游戏中寻找最佳移动。在竞争性...