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
类型。对于数组来说,和对象一样,在其原型上扩展的方法where
、groupBy
、has
也都被输出了。 或许会有疑问,数组不是有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
(forEach
为ES5中新增,需要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对象的each
和map
方法,但是结果会让你失望的,依旧会是循环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
方法,严格来算并不算是循环,原因在于,它并不能响应循环应有的break
和continue
语句。准确地讲,它是一个遍历函数,对每个元素执行指定的回调函数。
#for-of循环
ES6中又新增了for - of
循环,它与for - in
循环类似,但又有所不同。
for - of
支持对数组和类数组对象进行循环,不支持普通对象的循环。for - of
支持对字符串进行循环遍历。for - of
支持对Map
和Set
(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
代表的仅仅是对象的键名。