for...of循環
ES6借鑑C++、Java、C#和Python語言,引入了ˆ‘for...of 循環,作爲遍歷所有數據結構的統一的方法。一個數據結構只要部署了›„‘ŽSymbol.iterator 屬性,就被視爲具有iterator接口,就可以用ˆ‘ˆ‘for...of循環遍歷它的成員。也就是說,ˆ‘for...of 循環內部調用的是數據結構的›„‘Ž›„‘ŽSymbol.iterator 方法。
for...of循環可以使用的範圍包括數組、Set和Map結構、某些類似數組的對象(比如arguments對象、DOMNodeList對象)、後文的Generator對象,以及字符串。
1、數組
數組原生具備iterator接口,ˆ‘for...of循環本質上就是調用這個接口產生的遍歷器,可以用下面的代碼證明。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
<script src="js/traceur.js"></script>
<script src="js/bootstrap.js"></script>
<script type="text/traceur">
const arr=['red','green','blue'];
let iterator=arr[Symbol.iterator]();
for (let v of arr){
console.log(v);
//red
//green
//blue
}
for (let vv of iterator){
console.log(vv);
//red
//green
//blue
}
</script>
</head>
<body>
</body>
</html>
上面代碼的ˆ‘ˆ‘for...of 循環的兩種寫法是等價的。
for...of循環可以代替數組實例的 forEach 方法。…
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
<script src="js/traceur.js"></script>
<script src="js/bootstrap.js"></script>
<script type="text/traceur">
const arr=['red','green','blue'];
arr.forEach(function (element,index){
console.log(element);
console.log(index);
//red
//0
//green
//1
//blue
//2
});
</script>
</head>
<body>
</body>
</html>
JavaScript原有的for...in循環,只能獲得對象的鍵名,不能直接獲取鍵值。ES6提供ˆ‘for...of‘ˆ 循環,允許遍歷獲得鍵值。ƒ
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
<script src="js/traceur.js"></script>
<script src="js/bootstrap.js"></script>
<script type="text/traceur">
var arr=['a','b','c','d'];
for (let a in arr){
console.log(a); //0 1 2 3
}
for (let a of arr){
console.log(a); //a b c d
}
</script>
</head>
<body>
</body>
</html>
上面代碼表明,for...in循環讀取鍵名,for...of‘ˆ 循環讀取鍵值。如果要通過ˆ‘for...of 循環,獲取數組的索引,可以藉助數組實例的 entries‡‹‡• 方法和‡›• keys 方法。
ˆ‘
for...of循環調用遍歷器接口,數組的遍歷器接口只返回具有數字索引的屬性。這一點跟for...in循環也不一樣。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
<script src="js/traceur.js"></script>
<script src="js/bootstrap.js"></script>
<script type="text/traceur">
let arr=[3,5,7];
arr.foo='hello';
for (let i in arr){
console.log(i); //0 1 2 3 foo
}
for (let i of arr){
console.log(i); //3 5 7
}
</script>
</head>
<body>
</body>
</html>
上面代碼中,ˆ‘for...of循環不會返回數組ƒ arr 的ˆ‘‘ foo 屬性。
2、Set和Map結構
Set和Map結構也原生具有Iterator接口,可以直接使用ˆ‘for...of 循環。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
<script src="js/traceur.js"></script>
<script src="js/bootstrap.js"></script>
<script type="text/traceur">
var engines=new Set(["Gecko","Trident","Webkit","Webkit"]);
for (var e of engines){
console.log(e); //Gecko Trident Webkit
}
var es6=new Map();
es6.set("edition",6);
es6.set("committee","TC39");
es6.set("standard","ECMA-262");
for (var [name,value] of es6){
console.log(name + ":" +value);
//edition:6
//committee:TC39
//standard:ECMA-262
}
</script>
</head>
<body>
</body>
</html>
上面代碼演示瞭如何遍歷Set結構和Map結構。值得注意的地方有兩個,首先,遍歷的順序是按照各個成員被添加進數據結構的順序。其次,Set結構遍歷時,返回的是一個唯一的值,而Map結構遍歷時,返回的是一個數組,該數組的兩個成員分別爲當前Map成員的鍵名和鍵值。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
<script src="js/traceur.js"></script>
<script src="js/bootstrap.js"></script>
<script type="text/traceur">
let map=new Map().set('a',1).set('b',2);
for (let pair of map){
console.log(pair);
//["a", 1]
//["b", 2]
}
for (let [key,value] of map){
console.log(key +':'+value);
//a:1
//b:2
}
</script>
</head>
<body>
</body>
</html>
-------------------------------------------------------------
計算生成的數據結構
有些數據結構是在現有數據結構的基礎上,計算生成的。比如,ES6的數組、Set、Map都部署了以下三個方法,調用後都返回遍歷器對象。
(1)、entries() 返回一個遍歷器對象,用來遍歷[鍵名, 鍵值] 組成的數組。對於數組,鍵名就是索引值;對於Set,鍵名與鍵值相同。Map結構的iterator接口,默認就是調用entries方法。
(2)、 ‡›•keys()返回一個遍歷器對象,用來遍歷所有的鍵名。
(3)、values()返回一個遍歷器對象,用來遍歷所有的鍵值。
這三個方法調用後生成的遍歷器對象,所遍歷的都是計算生成的數據結構。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
<script src="js/traceur.js"></script>
<script src="js/bootstrap.js"></script>
<script type="text/traceur">
let arr=['a','b','c'];
for (let pair of arr.entries()){
console.log(pair);
//[0, "a"]
//[1, "b"]
//[2, "c"]
}
for (let pair of arr.keys()){
console.log(pair); //0 1 2
}
for (let pair of arr.values()){
console.log(pair); //a b c
}
</script>
</head>
<body>
</body>
</html>
--------------------------------------------------------------------
類似數組的對象
類似數組的對象包括好幾類。下面是for…of循環用於字符串、DOM NodeList對象、arguments對象的例子。
//字符串
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
<script src="js/traceur.js"></script>
<script src="js/bootstrap.js"></script>
<script type="text/traceur">
let str="hello";
for(let s of str){
console.log(s);
//h
//e
//l
//l
//o
}
</script>
</head>
<body>
</body>
</html>
// ‘†‡‹•對象DOM NodeList對象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
<script src="js/traceur.js"></script>
<script src="js/bootstrap.js"></script>
<script type="text/traceur">
let paras=document.querySelectorAll("p");
for (let p of paras){
p.classList.add("test");
}
</script>
</head>
<body>
</body>
</html>
//argument對象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
<script src="js/traceur.js"></script>
<script src="js/bootstrap.js"></script>
<script type="text/traceur">
function printArgs(){
for (let x of arguments){
console.log(x);
}
}
printArgs('a','b');
//a
//b
</script>
</head>
<body>
</body>
</html>
對於字符串來說,for...of循環還有一個特點,就是會正確識別32位UTF16字符。
並不是所有類似數組的對象都具有iterator接口,一個簡便的解決方法,就是使用Array.from方法將其轉爲數組。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
<script src="js/traceur.js"></script>
<script src="js/bootstrap.js"></script>
<script type="text/traceur">
let arrayLike={length:2,0:'a',1:'b'};
/*for (let x of arrayLike){
console.log(x);//報錯arrayLike[Symbol.iterator] is not a function
} */
for (let x of Array.from(arrayLike)){
console.log(x);
//a
//b
//因for...of循環輸出的是值,而且是索引爲數字的值,故鍵名爲length的值2不輸出
}
</script>
</head>
<body>
</body>
</html>
------------------------------------------------------------------------------
對象
對於普通的對象,for...of 結構不能直接使用,會報錯,必須部署了iterator接口後才能使用。但是,這樣情況下,for...in 循環依然可以用來遍歷鍵名。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
<script src="js/traceur.js"></script>
<script src="js/bootstrap.js"></script>
<script type="text/traceur">
var es6={
edition:6,
committee:"TC39",
standard:"ECMA-262"
};
for (e in es6){
console.log(e);
//edition
//committee
//standard
}
for (e of es6){
console.log(e); //報錯TypeError: es6[Symbol.iterator] is not a function
}
</script>
</head>
<body>
</body>
</html>
上面代碼表示,對於普通的對象,ˆ‘for...in循環可以遍歷鍵名,ˆ‘for...of循環會報錯。一種解決方法是,使用„Œ‡…Object.keys 方法將對象的鍵名生成一個數組,然後遍歷這個數組。ˆ
for (var key of Object.keys(someObject)){
console.log(key +":"+someObject[key]);
}
在對象上部署iterator接口的代碼,一個方便的方法是將數組的›„‘ŽSymbol.iterator屬性,直接賦值給其他對象的›„‘Ž›„‘ŽSymbol.iterator 屬性。比如,想要讓ˆ‘for...of 環遍歷jQuery對象,只要加上下面這一行就可以了。Œ‡›
jQuery.prototype[Symbol.iterator]=Array.prototype[Symbol.iterator];
另一個方法是使用Generator函數將對象重新包裝一下。
function* entries(obj){
for (let key of Object.keys[obj]){
yield [key,obj[key]];
}
}
for (let [key,value] of entries(obj)){
console.log(key,"->",value);
}
--------------------------------------------------------------
與其他遍歷語法的比較
以數組爲例,JavaScript提供多種遍歷語法。最原始的寫法就是for循環。
for (var index=0;index < myArray.length;index++){
console.log(myArray[index]);
}
這種寫法比較麻煩,因此數組提供內置的forEach方法。
myArray.forEach(function (value){
console.log(value);
});
這種寫法的問題在於,無法中途跳出 forEach 循環,break命令或return命令都不能奏效。
for...in 循環可以遍歷數組的鍵名。
for (var index in myArray){
console.log(myArray[index]);
}
for...in循環有幾個缺點。
(1)、數組的鍵名是數字,但是for...in循環是以字符串作爲鍵名“0”、“1”、“2”等等。
(2)、for...in循環不僅遍歷數字鍵名,還會遍歷手動添加的其他鍵,甚至包括原型鏈上的鍵。
(3)、某些情況下,for...in循環會以任意順序遍歷鍵名。
總之,for...in循環主要是爲遍歷對象而設計的,不適用於遍歷數組。
for...of循環相比上面幾種做法,有一些顯著的優點。
for (let value of myArray){
console.log(value);
}
(1)、有着同for...in一樣的簡潔語法,但是沒有for...in那些缺點。
(2)、不同用於forEach方法,它可以與break、continue和return配合使用。
(3)、提供了遍歷所有數據結構的統一操作接口。
下面是一個使用break語句,跳出for...of循環的例子。
for (var n of fibonacci){
if (n >1000){
break;
}
console.log(n);
}
上面的例子,會輸出斐波納契數列小於等於1000的項。如果當前項大於1000,就會使用break語句跳出for...of循環。