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.definePropertyenumerable: 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.definePropertyenumerable: falseにする方法だが、実際はenumerable属性のデフォルトがfalseなので特に指定しなくても列挙不可になる。つまりenumerable: trueと明示的に列挙可能にした場合のみ可能になる。

グローバルなオブジェクトへの追加は慎重にやるべき

しかしこういう方法はObjectArrayといったグローバルなオブジェクトを汚染する。

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だけでなくdata2prototype属性としても{d:250}が追加されている。

ObjectArrayといったグローバルなオブジェクトの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を配列でつかうべきではないというのが結論だ。