JavaScriptの反復処理(イテレーション)について
こんばんは、south37です。
最近、JavaScriptについて知れば知る程、ダメなところが目につく様になってきました。 もちろん良いところもいっぱいあって、関数が第一級オブジェクトである事や、シングルスレッドのイベントループという形式と非同期APIによって多数の処理を高速にさばける事なんかは本当にクールだと思います。
ただ、イケてない部分が多いのは事実で、その内の一つであるJavaScriptの反復処理に付いて今日は書きたいと思います。
反復処理の書き方
さて、JavaScriptにおいて反復処理をしたい場合はどんなコードを書いたら良いのでしょうか? 結論を先に述べると、Arrayに対しては
var array = [1, 2, 3]; for (var i = 0, n = array.length; i < n; i++ ) { console.log(array[i]); } // 出力 // 1 // 2 // 3
のように単純なfor
ループを用いると良いでしょう。また、単なるObjectに対しては、
var obj = {name: 'Taro', age: 18, gender: 'male'}; for (var key in obj) { if (obj.hasOwnProperty(key)) { console.log(key + ': ' + obj[key]); } } // 出力 // name: Taro // age: 18 // gender: male
のようにfor in
構文を用いながらも、hasOwnProperty
メソッドで直接のプロパティであるかを確認するようにすると良いでしょう。こうすれば、ほどんどの場合は想定通りの振る舞いになるはずです。
何故こんな書き方?
それでは、何故こんな書き方をしなければ行けないのでしょうか?特に、for in
構文で何故hasOwnProperty
を用いたチェックが必要になるのでしょうか?
実は、JavaScriptにおけるfor in
では、オブジェクトのプロトタイプチェーンを遡って全てのプロパティを列挙しようとします。その為、例えばユーティリティ関数としてeach
メソッドをObject.prototype
に追加したとすると、for in
を使った際にはそのeach
メソッド自身が列挙される事になります。
Object.prototype.each = function(iterator) { for (var key in this) { iterator(key, this[key]); } }; var obj = {name: 'Taro', age: 18, gender: 'male'}; obj.each(function(k, v) { console.log(k + ': ' + v); }); // 出力 // name: Taro // age: 18 // gender: male // each: function (iterator) { // for (var key in this) { // iterator(key, this[key]); // } // }
これは、どう考えても望ましい挙動ではありません。
一方、hasOwnProperty
メソッドでチェックを行っておけば、プロトタイプチェーンを遡ったりはせず、オブジェクト自身のプロパティに対してのみ処理が実行されます。
Object.prototype.each = function(iterator) { for (var key in this) { if (this.hasOwnProperty(key)) { iterator(key, this[key]); } } }; var obj = {name: 'Taro', age: 18, gender: 'male'}; obj.each(function(k, v) { console.log(k + ': ' + v); }); // 出力 // name: Taro // age: 18 // gender: male
こちらの方が直感通りの挙動ですね!
実際には、Object.prototype
にプロパティを追加する事自体が望ましくないのですが、意図せず書き換えられるケースも考慮してhasOwnPropery
によるチェックを行った方が良いと思います。
尚、ECMA5環境であれば、Object.keys
メソッドを使うといった方法も考えられます。こちらは、オブジェクトの直接のプロパティのkeyをArrayにして返すメソッドです。
var obj = {name: 'Taro', age: 18, gender: 'male'}; var keys = Object.keys(obj); for (var i = 0, n = keys.length; i < n; i++) { console.log(keys[i] + ': ' + obj[keys[i]]); } // 出力 // name: Taro // age: 18 // gender: male
想定通りの振る舞いですね。
Arrayの反復について
次に、Arrayの場合について考えてみたいと思います。Arrayもオブジェクトである為、for in
構文自体は使えるのですが、Objectの場合と同じ理由からあまり推奨されていません。
代わりに使われるのが、単純なfor
ループです。Arrayはlengthプロパティを持つ事やkeyとして数字を持つ事が保証されている為、最初の例のようにfor
ループが使えます。
var array = [1, 2, 3]; for (var i = 0, n = array.length; i < n; i++ ) { console.log(array[i]); } // 出力 // 1 // 2 // 3
また、for
に加えてECMA5ではforEach
メソッドという便利なメソッドが用意されました。これは、iteration用の関数を受け取って、Arrayの各要素を引数として実行してくれるメソッドです。
var array = [1, 2, 3]; array.forEach(function(el) { console.log(el); }; // 出力 // 1 // 2 // 3
forEach
メソッドを用いた方が処理そのものに注目しやすい為、ECMA5環境では積極的に使った方が良いでしょう。iteration用の関数には第二引数としてインデックスも渡せる為、for
ループを用いる場合に比べて出来る事に変わりはありません。
var array = [1, 2, 3]; array.forEach(function(e, i) { console.log('No.' + i + ': ' + e); }); // 出力 // No.0: 1 // No.1: 2 // No.2: 3
結論
最初に述べた通りです。もっと言えば、underscore.jsの_.each(list, iterator)
メソッドでは第一引数がArrayかObjectかに応じて上記のコードを使い分けて実行してくれる為、underscore.js使っとけば間違いないです。
って事で、underscore.jsのステマでした!!!!
補足
for in
でプロトタイプのプロパティが列挙されるといいましたが、システムでデフォルトで定義されているプロパティは列挙されません。後から追加したプロパティだけが列挙されます。なんでやねんって思うかもですが、そういう仕様です。
が、実は列挙不可のプロパティを追加する方法は存在します。ECMA5で定義されたObject.defineProperty
メソッドを使う事でそれが可能です。
Object.defineProperty(Object.prototype, 'each', { enumerable: false, configurable: false, writable: false, value: function(iterator) { for (var key in this) { iterator(key, this[key]); } } }); var obj = {name: 'Taro', age: 18, gender: 'male'}; obj.each(function(k, v) { console.log(k + ': ' + v); }); // 出力 // name: Taro // age: 18 // gender: male
Object.defineProperty
は第一引数にプロパティを追加したいオブジェクト、第二引数にプロパティのキー、第三引数にディスクリプタと呼ばれるプロパティの詳細を記述したオブジェクトを渡します。value
がプロパティの実際の値になります。
ディスクリプタ中のenumerable
という項目をfalse
に設定する事で、for in
による列挙が行われなくなっています。
ただ、このObject.defineProperty
メソッドを使うという方法は、ECMA5以降でしか動かない上かなりトリッキーな部類に入るので、よっぽどの理由が無い限り使わない方が良いと思います。大人しく_.each
を使っときましょう。
Effective JavaScript JavaScriptを使うときに知っておきたい68の冴えたやり方
- 作者: David Herman,吉川邦夫
- 出版社/メーカー: 翔泳社
- 発売日: 2013/02/19
- メディア: 大型本
- 購入: 1人 クリック: 109回
- この商品を含むブログを見る