一。役割
配列の走査に使用され、for...in でオブジェクトを走査するのと似ています
for (var i = 0; i < arr.length; i++) は使い勝手が良いですが、面倒くさい場合は arr.forEach() も使えます。では for...in が存在する意義は何でしょうか?
for (var i = 0; i < arr.length; i++)は簡潔ではありません
長すぎます。長いと間違いやすくなります。例えば筆者がよく犯す間違い:for (var j = 0; j < arr[i].length; i++)
- forEach は柔軟性に欠けます
arr.forEach は使いにくいです。break または return で抜け出すことができないためです
for...inは配列の走査には適していません
for...in はカスタムプロパティやプロトタイププロパティまで走査してしまい、index は数値ではなく文字列になり、場合によっては順序通りに走査されないこともあります
したがって、for...in でオブジェクトを走査するのと同じくらい簡単で使いやすい、配列を走査するより便利な方法が必要です。それが for...of です
二。特徴
###1.他のコレクションも走査可能
配列だけでなく、他のコレクション(iterable)も走査できます。NodeList/arguments、文字列、Map、Set などを含みます
サンプルコードは以下の通り:
// 配列
var arr = [1, 2, 2, 4];
for (var val of arr) {
console.log(val);
}
// 配列類似オブジェクト(arguments、NodeList)
(function() {
for (var val of arguments) {
console.log(val);
}
})(1, 2, 4);
// Set
var uniqueArr = new Set(arr); // Set 自動重複除去
for (var val of uniqueArr) {
console.log(val);
}
// Map
var map = new Map([['a', 1], ['b', 2]]);
map.set('c', 3);
for (var [key, val] of map) {
console.log('map[' + key + '] = ' + val);
}
注意:Chrome47 は var [key, val] of map 構文をサポートしていません。FF43 はサポートします。node --harmony v0.12.7 は分割代入式をサポートしていません。ES6 を快適に体験したい場合、node に think.js をインストールすることをお勧めします
###2.オブジェクトは走査不可
for...of はオブジェクトの走査をサポートしていませんが、[Symbol.iterator]() メソッドを追加することで、他のオブジェクト(例えば jQuery オブジェクト)が for...of 走査をサポートできるようになります。[Symbol.iterator]() メソッドを持つオブジェクトは反復可能(iterable)です
どのようにサポートしていないのか?サンプルコードは以下の通り:
var obj = {
'a': 1,
'b': 2
}
for (var val of obj) {
console.log(val);
}
// Uncaught TypeError: is not a function
// エラー、オブジェクトの走査をサポートしていません。それは for...in の役割です
カスタムオブジェクトが for...of 走査をサポートしたい場合、[Symbol.iterator]() メソッドを追加する必要があります。以下の通り:
// 一般メソッドでイテレーターを実装
class MyIterator {
constructor(obj) {
this.obj = obj;
this.keys = Object.keys(obj);
this.index = 0;
}
// iterator インターフェースを実装
[Symbol.iterator]() {
return this;
}
next() {
var val = this.obj[this.keys[this.index]];
if (this.index === this.keys.length) {
return {
done: true
}
}
else {
this.index++;
return {
done: false,
value: val
}
}
}
}
// テスト
var _obj = {
a: 2,
b: 1
}
for (var val of new MyIterator(_obj)) {
console.log(val);
}
ちょっと待って、Symbol とは何でしょうか?Symbol には大きな由来があります:Symbol は js の第 7 番目の基本タイプです(元々あった 6 つは null, undefined, Number, Boolean, Object, String)。文字列でもオブジェクトでもありません。証拠は以下の通り:
typeof Symbol() === 'symbol'
上記コードに現れる Symbol.iterator はビルトインの関数名です(Symbol タイプで、通常の関数名は String)。for...of 文を実行する際、インタープリターは of 后面的オブジェクト上で Symbol.iterator という名前のメソッドを探します。該メソッド実行後にイテレーターオブジェクト iterator を返します。iterator を取得した後、当然 .next()、.next() と done まで呼び出します。プロセスは以下の通り:
// 配列
var arr = [1, 2, 2, 4];
// for (var val of arr) {
// console.log(val);
// }
var iter = arr[Symbol.iterator]();
while (true) {
var res = iter.next();
if (res.done) {
break;
}
else {
var val = res.value;
console.log(val);
}
}
もちろん、私たちのこの機能は弱すぎます。for...of の強力な点は break, continue, return をサポートすることで、古典的な for ループと同じです
###3.カスタムイテレーターをサポート
実は上記で既にカスタムイテレーターを実装しましたが、面倒なようです。これを使うくらいなら死んだ方がましです。ES6 標準を制定した人も当然これを考えました。generator 構文(function* + yield)と組み合わせることで、簡単にカスタムイテレーターを実装し、for...of 走査をサポートできます:
var obj = {
'a': 1,
'b': 2
}
// generator でイテレーターを実装
obj[Symbol.iterator] = function*() {
for (var key in this) {
yield this[key];
}
}
for (var val of obj) {
console.log(val);
}
瞬時にイテレーターを実装できました。これでずっと便利になりました
P.S. 実は私たちの MyIterator は厳密に言えば generator です。(イテレーターの)ジェネレーターです
三。まとめ
ES5 は forEach を導入しましたが、実際にはあまり役に立ちません。多くの場合、書いた forEach を古典的な for ループに書き換える必要があります。。forEach は柔軟性において every に劣りますが、every は新しい配列を作成します。。
好了、これで for...of ができました。forEach とはさようならです
###参考資料
- 《ES6 in Depth》:InfoQ 中文站が提供する無料電子書籍
コメントはまだありません