前言
初學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 (<anonymous>)
at <anonymous>: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
*/
複製程式碼