• Object.keys()とfor…inは自身の列挙可能propertyだけか継承propertyも含むかで使い分ける

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の書きやすさだけで選ばずに済みます。

戻る