Object.keys()とfor…inは自身の列挙可能propertyだけか継承propertyも含むかで使い分ける
公開日: 執筆:佐藤
object自身が持つ列挙可能property名だけを取り出すなら、Object.keys()を使います。prototypeから継承した列挙可能propertyも含めて調べるなら、for...inを使います。
二つの違いは、配列を返すかloopを書くかだけではありません。自身のpropertyだけか、prototype chainまでたどるかを先に決めてください。
Object.keys()は自身の列挙可能property名を配列で返す
ECMAScript 5.1のObject.keys()は、引数のobjectが自身で持つpropertyのうち、[[Enumerable]]がtrueの名前を新しい配列で返します。
var report = {
source: 'newsletter',
visits: 120
};
var names = Object.keys(report);
// namesにはsourceとvisitsが入る
この例では、sourceとvisitsはobject literalが自身で持つ列挙可能propertyです。Object.keys()が返すのは値ではなくproperty名なので、値を読む場合はreport[names[i]]のように名前を使います。
列挙不可能なpropertyは、自身のpropertyでも結果へ入りません。Object.defineProperty()でenumerable: falseを指定した内部管理用の値などは、Object.keys()から除かれます。
for…inはprototype chainの列挙可能propertyも含む
for…inは、object自身だけでなく、prototype、その先のprototypeへと再帰的に列挙可能propertyをたどります。
次は、継承propertyが混ざることを確認する説明用の例です。
var defaults = {
currency: 'JPY'
};
var report = Object.create(defaults);
report.source = 'newsletter';
var names = [];
for (var name in report) {
names.push(name);
}
// namesにはsourceとcurrencyが入る。並び順は前提にしない
sourceはreport自身のpropertyです。currencyはdefaultsから継承したpropertyですが、列挙可能なのでfor...inの対象になります。
prototype側とobject自身に同じ名前のpropertyがある場合は、手前にあるpropertyがprototype側を隠します。同じ名前を二回処理する目的でfor...inを使うことはできません。
for…inで自身のpropertyだけを処理するなら明示的に絞る
既存のloopをfor...inで書く必要があり、自身のpropertyだけを処理したい場合は、hasOwnProperty()で確認します。
for (var name in report) {
if (Object.prototype.hasOwnProperty.call(report, name)) {
console.log(name, report[name]);
}
}
hasOwnProperty()は、指定した名前をobject自身が持つ場合だけtrueを返し、prototype chainは調べません。
report.hasOwnProperty(name)と直接呼ぶと、data自身にhasOwnPropertyという同名propertyがある場合や、Object.prototypeを継承しないobjectで使えません。どの形のobjectが渡されるか分からない共通処理では、Object.prototype.hasOwnProperty.call()を使うと、判定に使うmethodを固定できます。
列挙順を表示順や処理順へ使わない
ECMAScript 5.1は、for...inがpropertyを列挙する仕組みと順序を指定していません。実装がfor...inの順序を決めている場合、Object.keys()も同じ順序を使いますが、どの順序になるかを仕様からは決められません。
画面の表示順、送信する列順、処理の優先順位が必要なら、propertyの列挙順に任せず、名前を配列へ明示します。
var columns = ['source', 'visits'];
for (var i = 0; i < columns.length; i += 1) {
var name = columns[i];
console.log(name, report[name]);
}
また、列挙中にpropertyを追加しても、そのloopで訪問される保証はありません。まだ訪問していないpropertyを削除すれば、そのpropertyは訪問されません。loop中にobjectの形を変える処理と、propertyを読む処理を分ける方が結果を追いやすくなります。
欲しい範囲から方法を選ぶ
| 欲しいproperty | 選ぶ方法 | 補足 |
|---|---|---|
| 自身の列挙可能property | Object.keys() |
property名の配列を受け取る |
| 継承を含む列挙可能property | for...in |
prototype chainまで対象になる |
for...inで自身だけ |
hasOwnProperty()で絞る |
継承propertyを除外する |
| 列挙不可能propertyを含む一覧 | この二つでは取れない | 別の方法を検討する |
| 決めた順序のproperty | 名前の配列を用意する | 列挙順へ依存しない |
for...inを使ったあとで継承propertyを偶然除くより、最初から自身のpropertyだけが必要ならObject.keys()を選ぶ方が意図を読み取りやすくなります。継承propertyを使う設計なら、なぜprototype chainを含めるかをcodeの近くへ残します。
最初の一手は、列挙したいobjectに対して「自身だけ」「継承を含む」「列挙不可能も含む」「順序が必要」のどれかを一つ選ぶことです。その答えが決まれば、Object.keys()とfor...inをloopの書きやすさだけで選ばずに済みます。
関連記事

