[Effective JavaScript 笔记]第50条:迭代方法优于循环

"懒"程序员才是好程序员。复制和粘贴样板代码,一但代码有错误,或代码功能修改,那么程序在修改的时候,程序员需要找到所有相同功能的代码一处处进行修改。这会使人重复发明轮子,而且在别人看代码的时候无法在更高层次都看待问题的解决方案。太容易陷入细节。

for循环

js中的for循环在进行一些细微变化时,可以引入不同的行为。编程的时候对于边界条件的判断往往会导致一些简单的错误。下面的一些for循环的细微变化导致边界条件的变化。

for(var i=0;i<=n;i++){...}
//包括最后的迭代
for(var i=1;i< n;i++){...}
//忽略第一次迭代
for(var i=n;i>=0;i--){...}
//包括第一次迭代
for(var i=n-1;i > 0;i++){...}
//忽略最后的迭代

在这里都是对终止条件的一个设置。这里可以有很多的方式,可以使终止条件发生错误。
js的闭包是一种为这些模式建立迭代抽象方便的、富有表现力的手法,可以避免重复代码。

ES5中数组方法

其中ES5中提供的数组方法就包含很多。比如

Array.prototype.forEach

比如下面这段对数组的循环。

for(var i=0,n=players.length;i< n;i++){
    players[i].score++;
}

使用数组的迭代方法,可以改写为

players.forEach(function(p){
    p.score++;
});

上面的代码把循环的方式进行抽象,把要执行的具体代码通过函数传递闭包,完成对数组元素的操作及访问。这里消除了终止条件和任何数组索引。

Array.prototype.map

完成对数组的每个元素进行一些操作后建立一个新的数组。

使用循环
var trimmed=[];
for(var i=0,n=input.length;i< n;i++){
    trimmed.push(input[i].trim());
}
使用forEach
var trimmed=[];
input.forEach(function(s){
    trimmed.push(s.trim());
});
使用map
var trimmed=input.map(function(s){
    return s.trim();
});

Array.prototype.filter

计算一个数组建立一个新数组,该数组只包含有数组的一些元素
filter方法需要一个谓词函数,如果元素应该存在于新数组中则返回真值,如果元素应该被剔除则返回假值。例如,可以从价格表中提取出一个特定价格区间的列表。

listings.filter(function(listing){
    return listing.price>=min&&listing.price<=max;
});

以上的forEach,map,filter三个方法都是ES5中,数组的默认方法。下面实现一个自己的迭代抽象。
例:提取出满足谓词函数的数组的前几个元素,直到不满足的元素终止,不管后面是否有元素满足条件。

function takeWhile(a,pred){
   var res=[];
   for(var i=0,n=a.length;i < n;i++){
      if(!pred(a[i],i)){
          break;
      }
      res[i]=a[i];
   }
   return res;
}

var prefix=takeWhile([1,2,3,4,26,18,9],function(n){
    return n<10;
});
prefix;//[1, 2, 3, 4]

上面pred函数有两个参数,而下面的回调我们只传入了一个参数。对第二个参数没有进行处理,这里是无所谓的。

猴子补丁

把takeWhile函数添加到Array.prototype中使其成为一个方法。

Array.prototype.takeWhile=function(pred){
    var res=[];
    for(var i=0,n=this.length;i < n;i++){
      if(!pred(this[i],i)){
          break;
      }
      res[i]=this[i];
    }
    return res;
};
var prefix=[1,2,3,4,26,18,9].takeWhile(function(n){
    return n<10;
});
prefix;//[1, 2, 3, 4]

循环控制流操作

只有一点循环优于迭代,循环可以控制流操作,如break和continue。
例如,使用forEach方法来实现takeWhile函数

function takeWhile(a,pred){
    var res=[];
    a.forEach(function(x,i){
        if(!pred(x)){
          //?
        }
        res[i]=x;
    });
    return res;
}

上面的代码怎么终止该循环的当次执行。

内部异常

function takeWhile(a,pred){
    var res=[];
    var earlyExit={};
    try{
        a.forEach(function(x,i){
            if(!pred(x)){
              throw earlyExit;
            }
            res[i]=x;
        });
    }catch(e){
        if(e!==earlyExit){
            throw e;
        }
    }
    return res;
}

这样的处理方法,把原本简单的处理变得更加复杂了,不可取。

some,every

有没更简单的方法呢。下面看一下数组提供的用于提前终止循环的方法。some和every。

some方法返回一个布尔值表示其回调对数组的任何一个元素是否返回一个真值。

本人理解:
所有元素,对于传入的函数的判断,有一真则为真。全为假才为假。
相当于所有元素执行函数后取或。

[1,10,100].some(function(x){return x>5;});//true
[1,10,100].some(function(x){return x<0;});//false
every方法返回一个布尔值表示其回调函数是否对所有元素返回一个真值。

本人理解:
所有元素,对于传入的函数的判断,有一假则为假。全为真才为真。
相当于所有元素执行函数后取且。

[1,10,100].every(function(x){return x>5;});//false
[1,10,100].every(function(x){return x>0;});//true

这两个方法都是短路循环。只要产生的结果可以决定最后结果后,就不再执行后面的循环。即some一旦产生一个真值,则立即返回。every一旦产生一个假值,也立即返回。
利用它们的特点,改写takeWhile函数。

function takeWhile(a,pred){
    var res=[];
    a.every(function(x,i){
        if(!pred(x)){
           return false;//break
        }
        res[i]=x;
        return true;
    });
    return res;
}

提示

  • 使用迭代方法替换for循环使得代码可读,并且避免了重复循环控制逻辑
  • 使用自定义的迭代函数来抽象未被标准库支持的常见循环模式
  • 在需要提前终止循环的情况下,仍然推荐使用传统的循环。另外,some和every方法也可用于提前退出

相关阅读

时间: 2024-12-24 13:34:04

[Effective JavaScript 笔记]第50条:迭代方法优于循环的相关文章

[Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+1}" 反射获取函数源代码的功能很强大,使用函数对象的toString方法有严重的局限性.toString方法的局限性ECMAScript标准对函数对象的toString方法的返回结果(即该字符串)并没有任何要求.这意味着不同的js引擎将产生不同的字符串,甚至产生的字符串与该函数并不相关. 如果函数

[Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式传递给eval函数以达到同样的功能.程序员面临一个选择:应该将代码表示为函数还是字符串?毫无疑问,应该将代码表示为函数.字符串表示代码不够灵活的一个重要原因是:它们不是闭包. 闭包回顾 看下面这个图 js的函数值包含了比调用它们时执行所需要的代码还要多的信息.而且js函数值还在内部存储它们可能会引用

[Effective JavaScript 笔记] 第4条:原始类型优于封闭对象

js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //"number" typeof "s";//"string" typeof null;//"object":ECMAScript把null描述为独特的类型,但返回值却是对象类型,有点困惑. 可以使用Object.prototype.t

[Effective JavaScript 笔记]第60条:支持方法链

无状态的API的部分能力是将复杂操作分解为更小的操作的灵活性.一个很好的例子是字符串的replace方法.由于结果本身也是字符串,可以对前一个replace操作重复执行替换.这种模式的一个常见用例是在将字符串插入到HTML前替换字符串的特殊字符字母. function escapeBasicHTML(str){ return str.replace(/&/g,"&") .replace(/< /g,"<") .replace(/>/

[Effective JavaScript 笔记]第49条:数组迭代要优先使用for循环而不是for...in循环

示例 下面代码中mean的输出值是多少? var scores=[98,74,85,77,93,100,89]; var total=0; for(var score in scores){ total+=score; } var mean=total/scores.length; mean;//? 17636.571428571428 用计算器手动算了一下,答案应该是88.说明这段代码的真正结果应该是88,但为什么实际结果不正确呢.这和for...in循环会枚举所有key,包括原型中的.也就是

[Effective JavaScript 笔记]第45条:使用hasOwnProperty方法以避免原型污染

之前的43条,44条讨论了属性的枚举,但都没有彻底地解决属性查找中原型污染的问题.看下面关于字典的一些操作 'zhangsan' in dict; dict.zhangsan; dict.zhangsan=22; js的对象操作总是经继承的方式工作的.即使是一个空的对象字面量也是继承了Object.protoype属性. var dict={}; 'zhangsan' in dict;//false 'lisi' in dict;//false 'wangwu' in dict;//false'

[Effective JavaScript 笔记]第51条:在类数组对象上复用通用的数组方法

前面有几条都讲过关于Array.prototype的标准方法.这些标准方法被设计成其他对象可复用的方法,即使这些对象并没有继承Array. arguments对象 在22条中提到的函数arguments对象.它是一个类数组对象,并不是一个标准的数组,所以无法使用数组原型中的方法,因此无法使用arguments.forEach这样的形式来遍历每一个参数.这里我们必须使用call方法来对使用forEach方法. function highlight(){ [].forEach.call(argume

[Effective JavaScript 笔记]第26条:使用bind方法实现函数的柯里化

bind方法的作用,除了有绑定函数到对象外,我们来看看bind方法的一些其它应用. 简单示例 例子:假设有一个装配URL字符串的简单函数.代码如下 function simpleURL(protocol,domain,path){ return protocol+'://'+domain+'/'+path; } 要将特定站点的路径字符串构建为绝对路径URL.可以使用ES5中数组的map方法来实现.如下 var paths=['wengxuesong/','wengxuesong/p/556048

[Effective JavaScript 笔记]第25条:使用bind方法提取具有确定接收者的方法

js里方法和属性值为函数,就像一个东西两种称呼一个样,比如土豆,也叫马铃薯,一个样.既然一样,那就可以对对象的方法提取出来为函数,然后把提取出来的函数作为回调函数直接传递给高阶函数. 高阶函数是什么 玩过套娃娃游戏没,没玩过,没事,我也没玩过.大致就是下面这个样子呃,好吧,这才是真正的.就是多层函数,以函数为参数或返回值的函数.有点绕,没事看看上面的图就明白了.想了解怎么实现个简单的请点这里.好了,函数拿出来了,给高阶函数做参数传进去了.这里面很容易会忘记把传进去的函数绑定到当前对象上,自由惯了