JS编程建议——31:使用迭代

建议31:使用迭代
任何可以用递归实现的算法都可以用迭代实现。迭代算法通常包括几个不同的循环,分别对应算法过程的不同方面。虽然迭代也会导致性能问题,但是使用优化的循环替代长时间运行的递归函数可以提高性能,因为运行一个循环比反复调用一个函数的开销要低。
例如,合并排序算法是最常用的以递归实现的算法。下面是一个简单的合并排序算法:

  1. function merge(left, right) {
  2. var result = [];
  3. while(left.length > 0 && right.length > 0) {
  4. if(left[0] < right[0]) {
  5. result.push(left.shift());
  6. } else {
  7. result.push(right.shift());
  8. }
  9. }
  10. return result.concat(left).concat(right);
  11. }
  12. function mergeSort(items) {
  13. if(items.length == 1) {
  14. return items;
  15. }
  16. var middle = Math.floor(items.length / 2), left = items.slice(0, middle), right = items.slice(middle);
  17. return merge(mergeSort(left), mergeSort(right));
  18. }
    这个合并排序代码相当简单直接,其中mergeSort()函数被调用得非常频繁。对一个超过1500项的数组进行操作,就可能在Firefox 上导致栈溢出。程序陷入栈溢出错误并不一定要修改整个算法,这说明递归不是最好的实现方法。合并排序算法还可以用迭代实现,例如:
  19. function mergeSort(items) {
  20. if(items.length == 1) {
  21. return items;
  22. }
  23. var work = [];
  24. for(var i = 0, len = items.length; i < len; i++) {
  25. work.push([items[i]]);
  26. }
  27. work.push([]);
  28. for(var lim = len; lim > 1; lim = (lim + 1) / 2) {
  29. for(var j = 0, k = 0; k < lim; j++, k += 2) {
  30. work[j] = merge(work[k], work[k + 1]);
  31. }
  32. work[j] = [];
  33. }
  34. return work[0];
  35. }
    在上面代码中,mergeSort()实现与上一个 merge函数同样的功能却没有使用递归。虽然迭代可能比递归合并排序慢一些,但是它不会像递归那样影响调用栈。将递归算法切换为迭代是避免栈溢出错误的方法之一。
时间: 2024-09-30 04:54:44

JS编程建议——31:使用迭代的相关文章

JS编程建议——22:少用函数迭代

建议22:少用函数迭代ECMA-262v4为本地数组对象新增加了一个forEach方法.此方法遍历一个数组的所有成员,并且在每个成员上执行一个函数.在每个元素上执行的函数作为forEach()的参数传进去,并在调用函数时接收3个参数:数组项的值.数组项的索引.数组自身.例如: items.forEach(function(value, index, array){ process(value); }); forEach在Firefox.Chrome和Safari等浏览器中为原生函数.另外,for

JS编程建议——29:准确使用循环体(1)

建议29:准确使用循环体(1)1.选择正确的循环体在大多数编程语言中,代码执行时间多数消耗在循环的执行上.在一系列编程模式中,循环是最常用的模式之一,因此也是提高性能必须关注的地方之一.理解JavaScript 中循环对性能的影响至关重要,因为死循环或长时间运行的循环会严重影响用户体验.JavaScript定义了4种类型的循环:第一种循环是标准的for循环,与C语言使用同样的语法: for (var i=0; i < 10; i++){ //循环体 } for循环是最常用的循环结构,它由4部分组

JS编程建议——3:减少全局变量污染

建议3:减少全局变量污染定义全局变量有3种方式:在任何函数外面直接执行var语句. var f = 'value'; 直接添加一个属性到全局对象上.全局对象是所有全局变量的容器.在Web浏览器中,全局对象名为window. window.f = 'value'; 直接使用未经声明的变量,以这种方式定义的全局变量被称为隐式的全局变量. f = 'value'; 为方便初学者在使用前无须声明变量而有意设计了隐式的全局变量,然而不幸的是忘记声明变量成了一个非常普遍的现象.JavaScript的策略是让

JS编程建议——8:谨慎使用运算符(1)

建议8:谨慎使用运算符(1)1.用===,而不用==JavaScript有两组相等运算符:===和!==.==和!=.===和!==这一组运算符会按照期望的方式工作.如果两个运算数类型一致且拥有相同的值,那么===返回true,而!==返回false.==和!=只有在两个运算数类型一致时才会做出正确的判断,如果两个运算数是不同的类型,会试图强制转换运算数的类型.转换的规则复杂且难以记忆,具体规则如下: '' == '0' // false 0 == '' // true 0 == '0' //

JS编程建议——21:推荐提高循环性能的策略(2)

建议21:推荐提高循环性能的策略(2)在每个循环中,每次运行循环体都要发生如下操作:第1步,在控制条件中读一次属性(items.length).第2步,在控制条件中执行一次比较(i < items.length).第3步,比较操作,观察条件控制体的运算结果是不是true(i < items.length == true).第4步,一次自加操作(i++).第5步,一次数组查找(items[i]).第6步,一次函数调用(process(items[i])).在这些简单的循环中,即使没有太多的代码,

JS编程建议——21:推荐提高循环性能的策略(1)

建议21:推荐提高循环性能的策略(1)每次运行循环体时都会产生性能开销,增加总的运行时间,即使是循环体中最快的代码,累计迭代上千次,也将带来不小的负担.因此,减少循环的迭代次数可获得显著的性能提升.例如: var iterations = Math.floor(items.length / 8), startAt = items.length % 8, i = 0; do { switch(startAt) { case 0: process(items[i++]); case 7: proce

JS编程建议——29:准确使用循环体(3)

建议29:准确使用循环体(3)如果for循环的循环条件比较复杂,不是简单的数值迭代,这时for语句就必须考虑如何把循环条件和循环语句联系起来才可以正确地执行整个for循环.因此,根据for结构的运算顺序,for语句首先计算第一个和第二个表达式,然后执行循环体语句,最后返回执行for语句的第三个表达式,如此循环执行.例如: for(var a = true, b = 1; a; b ++ ){ if(b > 9) // 在循环体内间接计算迭代的步长 a = false; alert(b); } 在

JS编程建议——30:使用递归模式

建议30:使用递归模式复杂算法通常比较容易使用递归实现.很多传统算法正是通过递归实现的,如阶乘函数. function factorial(n) { if(n == 0) { return 1; } else { return n * factorial(n – 1); } } 递归函数的问题:错误定义或缺少终结条件会导致函数长时间运行,使浏览器出现假死现象.此外,递归函数还会受到浏览器调用栈大小的限制. JavaScript引擎所支持的递归数量与JavaScript调用栈大小直接相关.只有IE

JS编程建议——55:不要拘泥于数字下标

建议55:不要拘泥于数字下标数组下标默认为大于或等于0的整数,不过JavaScript允许数组下标可以为任意表达式,甚至为任意类型数据.(1)文本下标为数组下标指定负值:var a = []; // 定义空数组直接量a[-1] = 1; // 为下标为-1的元素赋值很显然,上面的用法是非法的,因为这不符合语法规范.使用length属性检测,返回值为0,说明数组并没有增加长度,这是正确的,也很正常.但是,使用下面的方法可以读取该元素的值:alert(a.length); // 0,说明数组长度没有