JavaScript 循环方法
不涉及到具体绑定到 prototype 上的循环方式,即 XXXXX.prototype 中包含的循环方式(如 forEach, map)。
for
for 总共有三种循环方式,一个是传统的 for 循环体,一个是 for in,还有一种是 for of。
传统 for
这个语法大多数其他的编程语言也有,JS 中主要是在于变量的声明,如:
// ES5
for (var i = 0; i < 10; i++) {
console.log('es5');
}
// ES6
for (let i = 0; i < 10; i++) {
console.log('es6');
}
const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
这里语法没有什么特别大的区别,输出结果也都一致,主要区别就在于 var 和 let 造成的作用域和变量提升(hoisting)的问题。鉴于目前浏览器对于 ES6 的支持都挺好的,建议使用 let 而非 var。
这里不能使用 const,因为使用 const 声明的变量无法被修改,所以这里也就无法被 increment,从而导致报错:

var, let, const 的异同可以查看这篇笔记:var, let, const 的区别与相同
另外一个比较有趣的写法,也是之前没有注意到的,就是省略所有变量声明的写法:
for (;;) {
// do sth
}
这样会造成一个无限循环。
for...of
for...of 是一个 ES6 后实现的迭代方式,大多数情况下用来便利数组,不过它本身可以用来循环所有的 iterable 对象,其中包括:
- 数组
- 字符串
- TypedArray
- Map
- Set
- NodeList
- arguments
- generators
- 用户自定义的 iterable 对象
总体来说如果是搭配 ES6 以后新创建的一些 objects 与特性,其灵活度甚至比起传统 for 循环还要高,唯一的局限就在于无法获取下标。
下面是比较常见使用 for...of 的语法:
const arr = [1, 2, 3, 4, 5];
for (const val of arr) {
console.log(val);
}
const map = new Map([
['a', 1],
['b', 2],
['c', 3],
]);
for (const [key, val] of map) {
console.log(key, val);
}
const set = new Set(['a', 1, 'b', '2', 2]);
for (const setV of set) {
console.log(setV);
}

for...in
for...in 用于迭代对象中不包括 symbol 的可枚举字符串属性 enumerable string properties,并且,for...in 也可以用来迭代继承的可枚举字符串属性:
const obj = {
a: 'a',
b: 'b',
c: 'c',
};
for (const enumerable in obj) {
console.log(enumerable);
}
const obj2 = { d: 'd' };
obj2.__proto__ = obj;
for (const enumerable in obj2) {
console.log(enumerable);
}

while
while 主要就分 while 和 do-while,二者的区别不算太大,主要是 do-while 会至少运行一次再检测是否满足条件。
let i = 10;
while (i < 0) {
console.log('i < 10');
}
do {
console.log('at least log once');
} while (i < 10);

关键字
主要补一下 label……才发现原来 JS 也有啊……
break
break 可以用来打破当前的循环,换言之,如果在 nested loops 中,它会 break inner loop:
for (let i = 0; i < 5; i++) {
console.log('i', i);
for (let j = 0; j < 20; j++) {
// 这里一旦 j > 1 就会跳出当前循环
if (j > 1) break;
console.log('j', j);
}
}

continue
continue 会继续下一个循环,如:
for (let i = 0; i < 5; i++) {
// 一旦 i > 2,就会跳过下面的代码,重新进入循环
if (i > 2) continue;
console.log('i', i);
for (let j = 0; j < 20; j++) {
if (j > 1) break;
console.log('j', j, 'i', i);
}
}

label
label 之前我没怎么用过……好像只有在汇编的时候碰到过。
其主要用途是将一个 label 绑定一个 expression,随后跳到对应的 label 上。虽然非循环体也可以使用 label,不过大多数情况下还是循环体中比较符合使用规律:
for (let i = 0; i < 3; i++) {
let j = 3;
outer: do {
console.log(j);
for (let k = 0; k < 5; k++) {
// 一旦k === 3 就会跳到 outer,因为 outer 的条件不满足了,所以就会彻底终止 do-while 循环
if (k === 3) {
break outer;
}
console.log(k);
}
j++;
} while (j < 3);
}

ES6 后的其他循环方式
map 可以使用其本身提供的 keys() 和 values() 两个函数,set 则是只有 values。它们返回的都是 iterator,所以需要使用 iterator 的方法进行调用。
对象除了 for...in 之外还可以使用 Object.entries(), Object.keys(), Object.values() 的方式获取数组形式的键值对、键和值,因为返回的都是数组,所以可以使用传统的 for 循环。
the end
传统 for 循环写起来可能是最麻烦的,不过对于需要下标的循环来说灵活度也是最高的,在一些情况下确实能够解决 forEach 所带来的问题,不过传统 for 循环无法实现对于对象的循环。
for...of 的写法更加的简单,并且也可以使用 await 进行异步操作,并且搭配一些 ES6 后存在的 objects 与 attributes,其灵活性会更高,不过需要 index 的时候 for...of 就有它的局限性。
for...in 只能用来循环对象,关于什么时候使用获取对象的键值对的方法,可以参考这张表:

一些其他关于数组循环的笔记有:
-
JavaScript 循环中调用异步函数的三种方法,及为什么 forEach 无法工作的分析
-
JavaScript 的 foreach 用不了 break/continue?同样写法下 for 循环也不行
其他的 reference:
-
for…of
-
for…in

















