前言
初学JavaScript
的时候,知道有各种for
的时候,懵懵懂懂,也许是因为没有系统学习的缘故。现在我们把各种for
都挨个辨明。
一、for
创建一个循环,包含三个可选表达式。三个可选表达式在圆括号中,由分号分隔。后跟一个循环中执行的语句或块语句。
语法
for ([initialization]; [condition]; [final-expression]) statement
initialization
初始化语句。可写表达式、赋值语句、变量声明。
condition
循环条件表达式。如果表达式结果为true
,statement
会被执行。如果表达式结果为false
,那么执行流程跳到for
语句结构后面的第一条语句。不写表达式,就是永远为true
。
final-expression
每次循环的最后都要执行的表达式。执行时机是在下一次condition
的计算之前。
statement
只要condition
的结果为true
就会被执行的语句。多条语句使用块语句({...}
)来包含。没有语句执行,使用空语句(;
)。
示例
我想输出五个数字。
for (let i = 0; i < 5; i++)
console.log(i);
/*
0
1
2
3
4
*/
另一种写法输出五个数字。可选的三个表达式,多行语句,需要使用{}
包含起来。
for (let i = 0; ; i++) {
if (i >= 5)
break;
console.log(i);
}
/*
0
1
2
3
4
*/
注意,如果不写条件表达式,就要确保循环体内能够跳出,防止死循环。break
可以跳出循环。
二、for...in
以任意顺序遍历一个对象的可枚举属性。对于每个枚举的属性,
...
部分都会被执行。
语法
for (variable in object) {...}
variable
每次迭代的时候,将对象的属性名分配给变量。
object
被迭代枚举的对象。
示例
我想输出对象里所有的属性和值。
let o = {
a: 1,
b: 2,
c: 3
};
for (const v in o) {
console.log(`o.${v} = ${o[v]}`);
}
/*
o.a = 1
o.b = 2
o.c = 3
*/
可以看见for...in
把所有的可枚举属性都枚举了出来,v
的类型是String
,所以访问当前遍历到的属性值使用了关联数组o[v]
的方式。
for...in
在遍历的时候,是以任意顺序遍历的。
let o = [];
o[0] = 1;
o['one'] = 2;
o[2] = 3;
for (const v in o) {
console.log(`o[${v}] = ${o[v]}`);
}
/*
o[0] = 1
o[2] = 3
o[one] = 2
*/
因此当遇到对迭代访问顺序很重要的数组时,最好用整数索引。
我想累加数组所有的成员。
Array.prototype.age = 97;
let o = [1,2];
let sum = 0;
for (const v in o) {
sum += o[v];
console.log(`o[${v}] = ${o[v]}`);
}
console.log(`sum = ${sum}`);
/*
o[0] = 1
o[1] = 2
o[age] = 97
sum = 100
*/
很显然这里不符合我们的预期,因为for...in
循环语句将返回所有可枚举属性,包括非整数类型的名称和继承的那些。还会获取到原型链上的可枚举属性。
我只想累加自身所有属性。
Array.prototype.age = 97;
let arr = [1, 2];
let sum = 0;
for (const v in arr) {
if (arr.hasOwnProperty(v)) {
sum += arr[v];
}
console.log(`arr[${v}] = ${arr[v]}`);
}
console.log(`sum = ${sum}`);
/*
o[0] = 1
o[1] = 2
o[age] = 97
sum = 3
*/
如果你只要考虑对象本身的属性,而不是它的原型,那么使用Object.getOwnPropertyNames()
或执行Object.prototype.hasOwnProperty()
来确定某属性是否是对象本身的属性(也能使用propertyIsEnumerable
)。
三、Array.prototype.forEach()
对数组的每个元素执行一次提供的函数。返回值为
undefined
。
语法
Array.forEach(callback[, thisArg])
callback
为数组每个元素执行的函数,这个函数接受三个参数。
currentValue
数组中正在处理的当前元素值。
index
数组中正在处理的当前元素的索引。
array
forEach()
方法正在操作的数组。
thisArg
可选参数。当执行回调 函数时用作this
的值(参考对象)。
示例
我想输出所有元素。
function logArrayElements(element, index, array) {
console.log(`a[${index}] = ${element}`);
}
[4, 2, 3].forEach(logArrayElements);
/*
a[0] = 4
a[1] = 2
a[2] = 3
*/
forEcah()
会跳过已经删除或者为初始化的项(但不包括那些值为undefined
的项,例如在稀疏数组上)。
function logArrayElements(element, index, array) {
console.log(`a[${index}] = ${element}`);
}
[4, , 3].forEach(logArrayElements);
[1, undefined, 3].forEach(logArrayElements);
/*
a[0] = 4
a[2] = 3
a[0] = 1
a[1] = undefined
a[2] = 3
*/
没有办法终止会跳出forEcah()
循环,除了抛出一个异常。
function logArrayElements(element, index, array) {
console.log(`a[${index}] = ${element}`);
break;
}
[1, 2, 3].forEach(logArrayElements);
/*
Uncaught SyntaxError: Illegal break statement
at Array.forEach ()
at :5:11
*/
使用return
也无法中止循环。
使用thisArg
,举个勉强的例子。通过自定义的add()
方法,计算所添加数组的和sum
和成员数count
。
function Counter() {
this.sum = 0;
this.count = 0;
}
Counter.prototype.add = function(array) {
array.forEach(function(element) {
this.sum += element;
++this.count;
}, this);
};
let obj = new Counter();
obj.add([1, 3, 5, 7]);
console.log(obj.count); // 4 === (1+1+1+1)
console.log(obj.sum); // 16 === (1+3+5+7)
/*
4
16
*/
注意:如果使用箭头函数表达式传入函数参数,thisArg
参数会被忽略,因为箭头函数在词法上绑定了this
值。
如果数组在迭代时被修改了,则其他元素会被跳过。
let words = ["one", "two", "three", "four"];
words.forEach(function(word) {
console.log(word);
if (word === "two") {
words.shift();
}
});
/*
one
two
four
*/
当到达包含值"two"
的项时,整个数组的第一个项被移除了,这导致所有剩下的项前移了一个位置。因为元素"four"
现在在数组更前的位置,"three"
会被跳过。forEach()
不会在迭代之前创建数组的副本。
四、for...of
for...of
语句在可以迭代的对象(Array
、Map
、Set
、String
、TypedArray
、arguments
对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。
语法
for (variable of iterable) { ... }
variable
在每次迭代中,将不同属性的值分配给变量。
iterable
被迭代枚举其属性的对象。
示例
迭代Array
let a = [10, 20, 30];
for (let v of a) {
console.log(v);
}
/*
10
20
30
*/
迭代String
let s = 'Tang';
for (let v of s) {
console.log(v);
}
/*
T
a
n
g
*/
迭代arguments
(function() {
for (let v of arguments) {
console.log(v);
}
}
)(1, 2, 3);
/*
1
2
3
*/
区别
无论是for...in
还是for...of
语句都是迭代一些东西。它们之间的主要区别在于它们的迭代方式。
for...in
语句以原始插入顺序迭代对象的可枚举属性。
for...of
语句遍历可迭代对象定义要迭代的数据。
以下示例显示了与Array
一起使用时,for...of
循环和for...in
循环之间的区别。
Object.prototype.objCustom = function() {};
Array.prototype.arrCustom = function() {};
let iterable = [3, 5, 7];
iterable.foo = 'hello';
for (let i in iterable) {
console.log(i);
}
/*
0
1
2
foo
arrCustom
objCustom
*/
for (let i in iterable) {
if (iterable.hasOwnProperty(i)) {
console.log(i);
}
}
/*
0
1
2
foo
*/
for (let i of iterable) {
console.log(i);
}
/*
3
5
7
*/