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