JavaScript的for從懵懂到辨明

唐金健發表於2018-06-09

前言

初學JavaScript的時候,知道有各種for的時候,懵懵懂懂,也許是因為沒有系統學習的緣故。現在我們把各種for都挨個辨明。

一、for

建立一個迴圈,包含三個可選表示式。三個可選表示式在圓括號中,由分號分隔。後跟一個迴圈中執行的語句或塊語句。

語法

for ([initialization]; [condition]; [final-expression])
	statement
複製程式碼

initialization

初始化語句。可寫表示式、賦值語句、變數宣告。

condition

迴圈條件表示式。如果表示式結果為truestatement會被執行。如果表示式結果為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語句在可以迭代的物件(ArrayMapSetStringTypedArrayarguments物件等等)上建立一個迭代迴圈,呼叫自定義迭代鉤子,併為每個不同屬性的值執行語句。

語法

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
*/
複製程式碼

相關文章