Javascript の for...in
for...in命令
オブジェクトの要素を先頭から順に取り出して処理する。
■ for...inの構文
for (仮変数 in オブジェクト) { 命令 }
for...in
の使い方
オブジェクトのキーが先頭から順に仮変数に格納されて処理される。仮変数に格納されるのはあくまでオブジェクトのキーであって、値ではない。
// forin.js let data = {a: 100, b: 150, c: 200}; for (let key in data) { console.log(key + '=' + data[key]); }
$ node forin.js a=100 b=150 c=200
for...in は配列でも使えなくはないが...
配列でも for...in命令を利用することは可能。ただし後述するようないろんな問題がある。
let data = [100, 150, 200]; for (let key in data) { console.log(key + '=' + data[key]); }
$ node forin.js 0=100 1=150 2=200
配列を for...in で処理すると、仮変数に順に入るのは配列要素のインデックス。
for...in はprototype要素
を列挙してしまう
これを欠点というべきかどうかは使い方しだいなのだが、for...in
命令には列挙可能なprototype要素
まで処理してしまうという致命的なクセがある。
例えば配列の要素を単に列挙しようとすると、その配列のprototype要素
まで処理してしまう。
let data = ['A', 'B', 'C']; Array.prototype.foo = {D:'D'}; for (let key in data) { console.log(key + '=' + data[key]); }
$ node forin.js 0=A 1=B 2=C foo=[object Object]
もしArray.prototype.foo = function() {...}
なんて式があれば、実行結果にfoo=function() {...}
と出てきてしまう。これはたいてい意図しない結果だろう。
この点は列挙対象がオブジェクトであっても同じ。
let data = {a: 100, b: 150, c: 200}; Object.prototype.d = 250; for (let key in data) { console.log(key + '=' + data[key]); }
$ node forin.js a=100 b=150 c=200 d=250
正確に言えば、for...in は対象となるオブジェクトや配列の直属のプロパティ名だけでなく、列挙可能(enumerable: true)なプロトタイプ上のプロパティ名をも繰り返し取得してしまう。逆に言えば、プロトタイプ上のプロパティをも列挙して処理したい時には for...in が使えるということ。
for...in
で直属の要素だけを列挙する方法
要素の定義時にObject.defineProperty
で enumerable: false
にする
prototype
に何らかの要素を追加したが、どうしても直属の要素だけを列挙したい場合は、追加したいprototype要素
をObject.defineProperty
で列挙不可能なプロパティとして定義する。ただこれはprototype要素
を最初に追加定義する際にするべきで、定義した後の何らかのprototype要素
のenumerable属性
を変えたりはできない。
let data = {a: 100, b: 150, c: 200}; Object.defineProperty(Object.prototype, 'd', { value: 250, enumerable: false }); for (let key in data) { console.log(key + '=' + data[key]); }
$ node forin.js a=100 b=150 c=200
確かにprototype要素
は列挙されていない。
この方法はArray.prototype
に要素を追加する際にも使える。
let data = ['A', 'B', 'C']; Object.defineProperty(Array.prototype, 'foo', { value: {D: 'D'}, enumerable: false }); for (let key in data) { console.log(key + '=' + data[key]); }
$ node forin.js 0=A 1=B 2=C
このObject.defineProperty
でenumerable: false
にする方法だが、実際はenumerable属性
のデフォルトがfalse
なので特に指定しなくても列挙不可になる。つまりenumerable: true
と明示的に列挙可能にした場合のみ可能になる。
グローバルなオブジェクトへの追加は慎重にやるべき
しかしこういう方法はObject
やArray
といったグローバルなオブジェクトを汚染する。
let data1 = {a: 100, b: 150, c: 200}; Object.defineProperty(Object.prototype, 'd', { value: 250, enumerable: true }); for (let key in data1) { console.log(key + '=' + data1[key]); } let data2 = {E: 10, F: 15, G: 20}; for (let key in data2) { console.log(key + '=' + data2[key]); }
$ node forin.js a=100 b=150 c=200 d=250 E=10 F=15 G=20 d=250
実行結果を見ると、当然だがdata1
だけでなくdata2
のprototype属性
としても{d:250}
が追加されている。
Object
やArray
といったグローバルなオブジェクトのprototype属性
を弄っているのだから、そうすることが確実に必要だという計画無しにはやるべきではないと思う。ましてや単にfor...in
で列挙されたくないからという理由だけで使う方法ではないだろう。
hasOwnProperty
を使って判断する方法
そこでhasOwnProperty
というメソッドを使って、for...in
の中で直属のプロパティかどうかを調べて列挙するというもっと簡単な方法がある。
let data = {a: 100, b: 150, c: 200}; Object.prototype.d = 250; for (let key in data) { if (data.hasOwnProperty(key)) { console.log(key + '=' + data[key]); } }
$ node forin.js a=100 b=150 c=200
これが一番シンプル。ただしいちいち直属のプロパティかどうかを調べるので、パフォーマンス的には落ちるだろう。
for...in
は列挙の順番を保証できない
ただしfor...in
にはもうひとつ、列挙する順番が保証されていないという問題がある。
let data = {'6':1, '8':2, '-2':3, '-5':4, '6Ps':5, 'L':6, '0':7, '1':8, 'WWW':9, '4':10}; for (let key in data) { console.log(key + '=' + data[key]); }
オブジェクトのキー名には文字列が使えるから、こういうバラバラなキー名でも定義できる。しかし値は数値順なので定義された順に列挙されるなら値の数値は昇順に並ぶはず。
しかしfor...in
で列挙すると順序が何らかのルールで変えられてしまう。
$ node forin.js 0=7 1=8 4=10 6=1 8=2 -2=3 -5=4 6Ps=5 L=6 WWW=9
node.js
で実行しているが、一見したところ「整数値」ー>「負の整数値」ー>「文字列」の順になっているように見えるが、実行する環境によってこの順序が保証されるわけではないだろう。
for...in
がオブジェクトのキーを見に行って列挙するためのもので、オブジェクト(ハッシュとか連想配列とか呼ばれていたもの)の要素の順番は保証されておらず実装によって違いがあるのが普通なので、これはしかたがない。
ちなみに、こうしたテストを配列でもやってみたのだが、一見大きな問題は無いように見えた。しかしfor...in
が、列挙するキー順を保証しないことには変わりない。
この点では、やはりfor...in
を配列でつかうべきではないというのが結論だ。