[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方法也可用于提前退出
相关阅读
[Effective JavaScript 笔记]第50条:迭代方法优于循环的更多相关文章
- [Effective JavaScript 笔记]第64条:对异步循环使用递归
假设需要有这样一个函数,接收一个URL的数组并尝试依次下载每个文件直到有一个文件被成功下载.如果API是同步的,使用循环很简单实现. function downloadOneSync(urls){ f ...
- [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法
js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...
- [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象
js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...
- [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符
“1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...
- [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...
- [Effective JavaScript 笔记]第60条:支持方法链
无状态的API的部分能力是将复杂操作分解为更小的操作的灵活性.一个很好的例子是字符串的replace方法.由于结果本身也是字符串,可以对前一个replace操作重复执行替换.这种模式的一个常见用例是在 ...
- [Effective JavaScript 笔记]第45条:使用hasOwnProperty方法以避免原型污染
之前的43条,44条讨论了属性的枚举,但都没有彻底地解决属性查找中原型污染的问题.看下面关于字典的一些操作 'zhangsan' in dict; dict.zhangsan; dict.zhangs ...
- [Effective JavaScript 笔记]第20条:使用call方法自定义接收者来调用方法
不好的实践 函数或方法的接收者(即绑定到特殊关键字this的值)是由调用者的语法决定的.方法调用语法将方法被查找的对象绑定到this变量,(可参阅之前文章<理解函数调用.方法调用及构造函数调用之 ...
- [Effective JavaScript 笔记]第26条:使用bind方法实现函数的柯里化
bind方法的作用,除了有绑定函数到对象外,我们来看看bind方法的一些其它应用. 简单示例 例子:假设有一个装配URL字符串的简单函数.代码如下 function simpleURL(protoco ...
随机推荐
- SQL Server 收缩日志
一. SQL Server 2008 收缩日志 (1) 使用SQL管理器收缩日志 第一步执行如下命令 ALTER DATABASE platform SET RECOVERY SIMPLE GO 第二 ...
- mac 80端口映射 配置
mac 80端口映射 配置 macbook 下,要绑定 80 端口的话. 一种方式是用 root 权限启动,即 sudo 启动服务进程.但 sudo 指令存在一定的安全问题,能不使用的情况下我们都尽量 ...
- 序列化和反序列化的几种方式(DataContractSerializer)(二)
DataContractSerializer 类 使用提供的数据协定,将类型实例序列化和反序列化为 XML 流或文档. 无法继承此类. 命名空间: System.Runtime.Serializati ...
- WCF 入门 (17)
前言 看的是入门视频,就希望培养一个学习的习惯吧. 前段时间看了微软的SurfaceBook的视频,被惊艳到了,但是我没钱买啊... 第17集 WCF中未经处理的异常 Unhandled except ...
- linux 添加永久ip、路由和开启路由功能
一.添加永久ip 编辑/etc/sysconfig/network-scripts/ifcfg-eth0文件: 网络接口配置文件 [root@localhost ~]# cat /etc/syscon ...
- java多线程-CyclicBarrier
介绍 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point).在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBa ...
- u11-nav01
<header id="masthead" class="masthead" role="banner"> <nav cl ...
- iOS开发中的错误整理,启动图片设置了没有效果;单独创建xib需要注意的事项;图片取消系统渲染的快捷方式
一.启动图片设置了没有效果 解决方案:缓存啊!卸了程序重新安装吧!!!!! 二.单独创建xib需要注意的事项 三.图片取消系统渲染的快捷方式
- 从零开始设计SOA框架(二):请求/响应参数的设计
每个接口都有请求参数.响应参数.其中请求参数分为公共参数和业务参数.响应参数分为两类:正常的响应参数.统一的错误参数 一.请求参数 1.公共参数:每个接口都有的参数,主要包含appkey.时间戳. ...
- 【HDU 2546】饭卡(DP+贪心)
贪心:最贵的留到最后买.状态转移方程:dp[j]=dp[j+a[i]]|dp[j],dp[i]表示余下i元. 原来就不足5元,那就不能买啦. #include<cstdio> #inclu ...