分享

JavaScript之for循环重思

 燮羽 2011-11-23

如果你认为引入Array.prototype.forEach及它的朋友会产生dodo那样的for循环,请再想想。for循环老当益壮。

For循环经常被当作一只捣蛋的小马驹(难以驯服—译者按)。它最适合于解决经典的重复列举问题:

for (var i=0; i<=arr.length-1; i++) {
    //do something to each member
}

但随着更高等级命令函数的出现,无论是在本地程序还是在架构中,我们都可以写以下代码(或类似变异)

arr.forEach(function(each)) {
    //do something to each
});

讽刺的是,当高等级函数逐渐取代了老旧的传统模式,我们也终于可以从旧习惯中解脱出来,去探索一些更有意思的for循环模式。

开胃菜——这是一个超级简洁的方法去生成并显示出斐波那契数列中的前n位:

for (
    var i=2, r=[0,1];
    i<15 || alert(r);
    r.push(r[i-1] + r[i-2]), i++
);
//alerts "0,1,1,2,3,5,8,13,21,34,55,89,144,233,377"

 

基础

For循环的结构包括四个部分:

for (initialCode; iteratingCondition; repeatingExpression) {
    repeatingCode
}

- 所有4个部分都是非必要的。
-initialCode不需要被赋一个变量—任何有效的表达式都可以。
-iteratingCondition和repeatingExpression不能包含变量定义。
-当repeatingCode为空或只有一句,花括号就不是必要的。
当repeatingCode部分被执行后,repeatingExpression会被再次估值。

我们可以用伪代码的方式总结整个过程——(函数调用部分单纯是为了易读性)

initialCode();
while(iteratingCondition()) {
	repeatingCode();
	repeatingExpression();
}

探索它的模式

在这一章里,for循环的用法会从我们熟悉的样子变得有点古怪。这样做的意图是向你展示结构的多变和语句的力量。我并不是为了给你提供最好的练习范本。

常见的Array模式

for (var i=0; i<=arr.length-1; i++) {
    var member = arr[i];
    doSomething(member);
}

储存array长度以提高效率

for (var i=0, l=arr.length; i<=l-1; i++) {
    var member = arr[i];
    doSomething(member);
}

合并iteratingCondition和repeatingExpression

for (var i=arr.length; i--;) {
    var member = arr[i];
    doSomething(member);
}

这样做的原理是当i为0, 判定条件变为false,我们会退出循环。当然,只有我们可以使用逆向循环的时候,以上代码才可用。

在iteratingCondition中给变量赋值

我们可以把repeatingCode中的变量赋值移到theriteratingCondition中去。当each无定义时,循环就会被终止。

这样我们就缩短了代码的长度并且不需要检查数列长度。语法变得更直接,在我看来,也更优雅。只有当数列是连续的,并且不会有出现“falsey”值(null,0,空或false)的时候,这个方法才可用。

for (var i=0, each; each = arr[i]; i++) {
    doSomething(each);
}

稀疏数列测试

我们可以倒转以上的模式来主动地检查一个稀疏的数列或列表。这里我们高效地检测未定义参数:

var func = function(a,b,c) {
    for (var i=0; arguments[i] !== undefined; i++);
    var allArguments = (i >= arguments.callee.length);
    //...
}

无repeatingCode部分

repeatingCode和repeatingExpression有着相同的用途。所以如果重复代码部分可以简单地总结为一句代码,你可以删掉整个repeatCode部分。

function sum(arr) {
	for (var i=arr.length, r=0; i--; r += arr[i]);
	return r;
}

sum([3,5,0,-2,7,8]); //21

在iteratingCondition中藏一个finally子句

我们可以使用 逻辑布尔值||运算符 来定义一个final语句。这个小函数会把一个数组中的值相加,并在完成后打印出这个值。

function shoutOutSum(arr, x) {
	for (var i=arr.length, r=0; i-- || alert(r); r += arr[i]);
}

shoutOutSum([3,5,0,-2,7,8]); //alerts "21"

当然如果你的最终子句不返回falsey值的话,你就有麻烦了。你会陷入死循环。为保证这不会发生,你把final变量和false &&上。这会显得有点笨拙:

function sumAndMultiply(arr, x) {
	for (var i=arr.length, r=0; i-- || ((r = r*x) && false); r += arr[i]);
	return r;
}
sumAndMultiply([3,5,0,-2,7,8], 5); //105

更新:Brendan Eich建议用void来替代:

function sumAndMultiply(arr, x) {
        for (var i=arr.length, r=0; i-- || void (r = r*x); r += arr[i]);
	return r;
}

在initialCode部分无变量定义

initialCode不需要变量定义。为了不因variable hoisting产生混淆,很多程序员在函数的开头定义所有变量,有些JavaScript专家(包括Douglas Crockford)甚至不会再for循环中定义变量。

function myFunction(arr) {
	var i;
	//...
	for (i=0; i < arr.length; i++) {
		//...
	}
	//...
}

像我之前所说,你几乎一定会想用initialCode来给一些变量赋值,但这不是必须的。以下代码是一个挺烂的for循环,但我只是想用它来证明这一点。

var i = 0;
for (
    console.log('start:',+new Date);
    i<1000 || console.log('finish:',+new Date);
    i++
);

总结

本文只是列举了几种传统for循环语法的变形。毫无疑问,你还会用其它一些技术,我也很想听你说说看。我并不是建议你从明天开始就急匆匆地使用所有这些模式;即使你完全不用也没有关系!然而,旧瓶装新酒是加强对一个语言深度了解的好方法,最终也会保证这个语言自身的发展和成功。

继续阅读

ECMA-262, 5th edition
section 12.6.3 (The for statement)
sections 15.4.4.14 to 15.4.4.22 (High OrderArray Functions)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多