JavaScript 循环

JavaScript中直接提供的循环,主要有以下几种

while 循环

和其他语言一样,JavaScript中的while循环有两种形式:

while (condition) {
	// 循环内容
}

do {
	// 循环内容
} while (condition)

其中两种形式的不同在于,对condition判断的位置不同,前者先判断,再执行循环体;而后者先执行循环体一次,再进行条件判断。所以结果的不同就是后者能将循环内容至少执行一次。

for 循环

for循环的语法:

for (init; condition; step) {
    // 循环体代码块
}
  • init 语句 中的内容,会在循环代码开始前执行一次,通常用来声明一些变量。

  • condition 语句 会在每次循环开始时,进行判断。如果第一次就不满足,则不会进入循环。

  • step 语句 会在每次循环结束时执行,通常是递增或递减condition中的某个变量。

var arr = [0, 10, 20, 30];

for (var j = 0; j < arr.length; j++) {
    console.log(arr[j]);
}

// 如果在循环体中不会改变数组的长度,用一个变量存储数组长度是个更高效的选择
for (var i = 0, l = arr.length; i < l; i++) {
    console.log(arr[i]);
}

var k = 0,
    length = arr.length;
for (; k < length; k++) {
    console.log(arr[k]);
}

for循环中的init语句,通常用来声明初始变量,其可以有多个,用,分隔即可。而且由于此语句是在循环开始前执行,且只用执行一次,所以如果在外部已经声明过要用的变量了,for循环中的这个语句可以直接省略,如上例中的第三个for循环所示。

for循环的条件语句是在每次循环体开始前进行判断,如果为true,则执行循环体内容,否则结束循环。当此语句省略时,表示不进行条件判断,循环将一直执行,只有在循环中使用break来跳出循环。

for循环的step语句,最常见的就是使用++或者-- 运算符的表达式,不过也可以使用其他任意值的,而且也可以省略,换到循环体中进行改变赋值。

for-in 循环

for-in循环是JavaScript中专门提供用来遍历对象属性的。

var zs = {
    name: 'zhang san',
    gender: 'male',
    age: 26
};

for (var key in zs) {
    console.log('%s : %s', key, zs[key]);
}
// name : zhang san
// gender : male
// age : 26

不过需要注意一点问题,请看如下示例:

function Person(name, gender) {
    this.name = name;
    this.gender = gender;
}
Person.prototype.sayHello = function() {
    console.log('Hello,I am', this.name, '. I\'m a', this.gender);
};

var zs = new Person('zhang san', 'male');

for (var key in zs) {
    console.log('%s : %s',key, zs[key]);
}
// name : zhang san
// gender : male
// sayHello : function () {
//    console.log('Hello,I am', this.name, '. I\'m a', this.gender);
// }

这次循环遍历中,它还输出了zs原型链上的属性sayHello。这反应出一个问题,for - in 循环会遍历整个原型链,虽然可以使用hasOwnProperty方法进行过滤仅获取自身属性,但其访问的仍是整个原型链,遍历范围较广,所以其效率比较低,通常来说,其效率仅为普通for循环的1/7。

JavaScript中,由于数组也是对象,所以此方法也可以用来遍历数组。

var arr = [1, 2, 3, 4];
for (var i in arr) {
    console.log(typeof i);
    if (arr.hasOwnProperty(i))
        console.log('arr[%s] : %d', i, arr[i]);
    else
        console.log('arr.%s : ', i, arr.[i]);
}
// string
// arr[0] : 1
// string
// arr[1] : 2
// string
// arr[2] : 3
// string
// arr[3] : 4
// string
// arr.where : function ...
// string
// arr.groupBy : function ...
// string
// arr.has : function ...

这个输出的结果或许不是你想象的样子,然而他就是这样。我们认为的索引,实际是对象的键名,所以其是String类型。对于数组来说,和对象一样,在其原型上扩展的方法wheregroupByhas也都被输出了。 或许会有疑问,数组不是有length属性吗,为什么没有输出呢?原因在于数组的length属性是不可枚举属性,for - in循环不会访问这些属性。关于此的更多知识可参考MDN - 属性的可枚举性和所有权

Array 在** Javascript **中是一个对象, Array 的索引是属性名。事实上, Javascript 中的 “array” 有些误导性, Javascript 中的 Array 并不像大部分其他语言的数组。首先, Javascript 中的 Array 在内存上并不连续,其次, Array 的索引并不是指偏移量。实际上, Array 的索引也不是Number类型,而是String类型的。我们可以正确使用如 arr[0] 的写法的原因是语言可以自动将 Number 类型的 0 转换成 String 类型的 “0” 。所以,在 Javascript 中从来就没有Array的索引,而只有类似 “0” 、 “1” 等等的属性。

for - in 循环原本就是用来遍历对象的,用其来遍历数组并不合适,不过也有例外的情况,比如稀疏数组:

var arr = new Array(1000);

arr[0] = 1;
arr[99] = 3;
arr[999] = 5;
// for循环
for (var i = 0, l = arr.length; i < l; i++) {
    console.log('arr[%s]', i, arr[i]);
}
console.log('i :' , i);
// ...
// arr[0] 1
// ...
// arr[99] 3
// ...
// arr[999] 5
// i : 1000


// for - in 循环
var count = 0;
for(var j in arr){
	count ++ ;
	if(arr.hasOwnProperty(j)){
		console.log('arr[%s]', j, arr[j]);
	}
}
console.log('count : ', count);
// arr[0] 1
// arr[99] 3
// arr[999] 5
// i : 1000

直接使用普通的for循环,循环次数为数组长度1000次,而使用for - in循环,仅仅循环了3次。

上面看起来循环的效率是高了不少,但是在前面已经说过了,for - in 的专职是对象的遍历(类似的还有Object.keys()),因此上面的方案并非是一个完美的方案,更好的做法是用是数组本身的遍历方法forEach(forEachES5中新增,需要IE9+)。

var arr = new Array(1000);
arr[0] = 1;
arr[99] = 3;
arr[999] = 5;

var count = 0;
arr.forEach(function(value, index) {
    count++;
    console.log(typeof index);
    console.log(index, value);
});
console.log('count', count);
// number
// 0 1
// number
// 99 3 
// number
// 999 5
// count 3

这样就高效地进行了循环遍历,而且数组的索引index也被正确地识别为了number类型。

或许你会想到jQuery中提供的工具方法$.each()$.map()或者实例jQuery对象的eachmap方法,但是结果会让你失望的,依旧会是循环1000次,以下是示例:

var count1 = 0;
$.each(arr, function(index, value) {
    count1++;
    console.log(index, value);
});
console.log('count1',count1);
// count1 1000

var count2 = 0;
$.map(arr,function(value,index){
	count2++;
    console.log(index, value);
});
console.log('count2',count2);
// count2 1000

从上面对比来看Array.prototype.forEach方法似乎不错,但其循环效率仍然不如普通的for循环,不过优势在于在稀疏数组的处理。

不过forEach方法,严格来算并不算是循环,原因在于,它并不能响应循环应有的breakcontinue语句。准确地讲,它是一个遍历函数,对每个元素执行指定的回调函数。

for-of循环

ES6中又新增了for - of 循环,它与for - in循环类似,但又有所不同。

  • for - of 支持对数组类数组对象进行循环,不支持普通对象的循环。
  • for - of 支持对字符串进行循环遍历。
  • for - of 支持对MapSet (ES6 中新增的类型)对象遍历。
  • for - of 不支持普通对象的循环遍历。
// 数组
var arr = [0, 1, 2, 3, 4, 5];

for (let index of arr) {
    if (index === 1) continue;
    if (index === 4) break;
    console.log(typeof index);
    console.log(index, arr[index]);
}
// number
// 0 0
// number
// 2 2
// number
// 3 3

当索引为1时直接进行下一次循环,而当索引为4时,直接退出循环,因此输出如上结果,也同样正确识别索引为number类型。

// 类数组
// 类数组
var divs = document.querySelectorAll('div');

for (let i of divs) {
    console.log(i.classList);
}
// ["container", "md-doc", value: "container md-doc"]
// ["doc-cata", value: "doc-cata"]
// ["nicescroll-rails", "nicescroll-rails-vr", value: "nicescroll-rails nicescroll-rails-vr"]
// ["nicescroll-cursors", value: "nicescroll-cursors"]
// ["nicescroll-rails", "nicescroll-rails-hr", value: "nicescroll-rails nicescroll-rails-hr"]
// ["nicescroll-cursors", value: "nicescroll-cursors"]

for (let k in divs) {
    console.log(k, divs[k].classList);
}
// "0" ["container", "md-doc", value: "container md-doc"]
// "1" ["doc-cata", value: "doc-cata"]
// "2" ["nicescroll-rails", "nicescroll-rails-vr", value: "nicescroll-rails nicescroll-rails-vr"]
// "3" ["nicescroll-cursors", value: "nicescroll-cursors"]
// "4" ["nicescroll-rails", "nicescroll-rails-hr", value: "nicescroll-rails nicescroll-rails-hr"]
// "5" ["nicescroll-cursors", value: "nicescroll-cursors"]
// length undefined
// item undefined
// keys undefined
// values undefined
// entries undefined
// forEach undefined

for - of循环用于类数组对象的处理时,需要注意,of关键字前面的变量即为类数组对象中的键值,如上例所示,在for - of循环中i即代表着**DOM nodelist **中的每个对象,而在for - in 循环中k代表的仅仅是对象的键名。

参考链接


Last modified on 2016-12-19