for-inとObject.keysの違いを正しく知る


# はじめに

オブジェクトのキーを取得する方法には、次の２つの方法が知られています。
それは、`for-in`で繰り返し処理による取得と`Object.keys`による取得です。

実はこれいつでも同じ処理をするものだと思っていませんか？

```javascript
for (var p in obj) {
  if (obj.hasOwnProperty(p)) {
    //do something
  }
}

Object.keys(obj).forEach(function (p) {
  //do something
})
```

<strong>実は常に同じとは限りません</strong>

さてクイズです。
どのような時に同じにならないのかを考えてみてください。

正解はこのあと
　　・
　　・
　　・

# 早速、検証コードから見る

次のコードはまだ理解しなくいいですが、まずは実行した結果の違いをよく見てください。

```javascript
function showProperties (obj) {

  // 検証1 - keys
  // Object.keysを使う
  var keys = Object.keys(obj);
  console.log('keys:  ', keys);

  // 検証2 - forIn1
  // 列挙可能なキーを取得する
  var forIn1 = [];
  for(var p1 in obj) {
    forIn1.push(p1);
  }
  console.log('forIn1:', forIn1);

  // 検証3 - forIn2
  // 列挙可能なキーからプロトタイプのキーを除外する
  var forIn2 = [];
  for(var p2 in obj) {
    if (obj.hasOwnProperty(p2)) {
      forIn2.push(p2);
    }
  }
  console.log('forIn2:', forIn2);

  // 検証4 - names1
  // 列挙出来ないキーも取得する
  var names1 = Object.getOwnPropertyNames(obj);
  console.log('names1:', names1);

  // 検証5 - names2
  // プロトタイプの列挙出来ないキーも取得する
  var names2 = Object.getOwnPropertyNames(obj);
  var proto = Object.getPrototypeOf(obj);
  if (proto && proto !== Object.prototype) {
    names2 = names2.concat(Object.getOwnPropertyNames(proto));
  }
  console.log('names2:', names2);

  console.log(); // 空行
}

var obj;

//  --------------------------- pattern 1

obj = {
  a: 1,
  b: 2,
  c: 3
};

showProperties(obj);

// keys:   [ 'a', 'b', 'c' ]
// forIn1: [ 'a', 'b', 'c' ]
// forIn2: [ 'a', 'b', 'c' ]
// names1: [ 'a', 'b', 'c' ]
// names2: [ 'a', 'b', 'c' ]


//  --------------------------- pattern 2

function Fn () {
  this.a = 1;
  this.b = 2;
}
Fn.prototype.c = 3;

obj = new Fn();

showProperties(obj);

// keys:   [ 'a', 'b' ]
// forIn1: [ 'a', 'b', 'c' ]
// forIn2: [ 'a', 'b' ]
// names1: [ 'a', 'b' ]
// names2: [ 'a', 'b', 'constructor', 'c' ]

//  --------------------------- pattern 3

var original = {
  c: 4,
  d: 5
};

obj = Object.create(original, {
  a: {value: 1},
  b: {value: 2},
  c: {value: 3}
});

showProperties(obj);

// keys:   []
// forIn1: [ 'c', 'd' ]
// forIn2: [ 'c' ]
// names1: [ 'a', 'b', 'c' ]
// names2: [ 'a', 'b', 'c', 'c', 'd' ]


//  --------------------------- pattern 4

var original = {
  c: 4,
  d: 5
};

obj = Object.create(original, {
  a: {value: 1, enumerable: true},
  b: {value: 2},
  c: {value: 3}
});

showProperties(obj);

// keys:   [ 'a' ]
// forIn1: [ 'a', 'c', 'd' ]
// forIn2: [ 'a', 'c' ]
// names1: [ 'a', 'b', 'c' ]
// names2: [ 'a', 'b', 'c', 'c', 'd' ]

```

# 解説

クイズの正解は、結果のkeysとforIn2に差があるところで確認できます。
実はもう一つオブジェクトのキーを取得するのに`Object.getOwnPropertyNames`と言うのが存在します。これについては結果names1,names2で確認できます
これらの３つのキーの取得の方法の違いについて正しく理解します。

まずは、pattern1では全ての結果が同じになるようにkeys,forIn1,forIn2,names1,names2を揃えてました。
ここからどう違いが出るのかを見ます

## pattern 2

関数を`new`した時に生成されるオブジェクトを対象にしています
これによりプロトタイプが設定されたオブジェクトになっています
forIn1にありforIn2に無い`c`は、プロトタイプのキーのため`hasOwnProperty`の判定では`false`になります。
これは多くの書籍で説明されているので理解しやすいと思います。
まだkeysとforIn2に違いはありません。
names1,names2については次で詳しく解説します

## pattern 3

では`new`を使用しないでプロトタイプを指定したらどうなるでしょうか？
`Object.create`で`original`というオブジェクトをプロトタイプに設定した`obj`というオブジェクトを作成出来ます。
`Object.create`の第2引数は少々特別なオブジェクトの形式になっています。
`{a: 1, b: 2, c: 3}`の値の部分は`{a: {value: 1}, b: {value: 2}, c: {value: 3}}`などとする必要があります。
`Object.create`を`Object.defineProperty`に置き換えて記述すると次のようになります。

```javascript
obj = {};
obj.__proto__ = original;
Object.defineProperty(obj, 'a', {value: 1});
Object.defineProperty(obj, 'b', {value: 2});
Object.defineProperty(obj, 'c', {value: 3});
```

## keysとforIn2で違いが出たぞ！

さて、pattern3で結果keys,forIn2に違いが出ました。
実は、`Object.defineProperty`の第3引数の`{value: 1}`はオブジェクトの`1`を単純に値に設定する事とは異なります
つまり次のコードは上のコードとは処理が違います。

```javascript
obj = {};
obj.__proto__ = original;
obj.a = 1;
obj.b = 2;
obj.c = 3;
```

もし完全に同じとしたい場合は次のようにします。

```javascript
obj = {};
obj.__proto__ = original;
Object.defineProperty(obj, 'a', {value: 1, enumerable: true, writable: true, configurable: true});
Object.defineProperty(obj, 'b', {value: 2, enumerable: true, writable: true, configurable: true});
Object.defineProperty(obj, 'c', {value: 3, enumerable: true, writable: true, configurable: true});
```

とする必要があります。
javascriptではオブジェクトのプロパティに`public`,`private`などアクセス修飾子が存在しません。
そのかわり、列挙出来る出来ないの属性を持っています。それが、`enumerable`になります。
列挙できるプロパティは`Object.keys`や`for-in`でキーを取得する事が出来ます。

では、列挙できないプロパティの存在を知ることは出来るでしょうか？

はい、できます。
そんなときは`Object.getOwnPropertyNames`です。
`Object.getOwnPropertyNames`は非列挙も取得する`Object.keys`と思ってください。
そのため、結果のnames1,names2ではa,b,cが取得出来たのです。
　(※writable,configurableについての説明は補足で最後にしています)


## 前置きはいいから。で、なぜ違いがでたの？
それはpattern4の結果をみるとよくわかります。
pattern4では`a`だけ、列挙出来るように`enumerable`を追加しています。
そのため、`for-in`ではまず列挙可能なキーとして`a,b,c`のうち`a`だけにアクセス出来ます。
さらに`for-in`はプロトタイプのオブジェクトのキーも列挙可能であればアクセス出来ます。
そこで、`original`の`c,d`も列挙します。
結果forIn1では`a,c,d`が表示されています。
そしてforIn2で`hasOwnProperty`で`a,c,d`から絞り込みをした際に、`a,c`はobj自身がもっているプロパティのため、`a,c`が表示されていたと言う事です。
つまり列挙の`c`はプロトタイプから行われ、`hasOwnPropery`では`obj`自身の`c`から行われているため、異なる対象を判別に使用していたのです。
そのため、結果keysとforIn2で差が発生してしまったのです。

## 厳密に等価な処理は？

```javascript
var keys = Object.keys(obj);
```

を`for-in`と`hasOwnProperty`を使って厳密に`Object.keys`と同じ処理を記述すると次のようになります

```javascript
var keys = [];
for(var p in obj) {
  if (obj.hasOwnProperty(p)) {
    var dest = Object.getOwnPropertyDescriptor(obj, p);
    if (dest.enumerable) {
      keys.push(p);
    }
  }
}
```

`Object.getOwnPropertyDescriptor`はプロパティの定義の取得し列挙出来るかどうかを確認出来ます。

面倒くさいですね。素直に`Object.keys`を使いましょう。

## 補足・注意

今回の記事はECMAScript5以上であることで説明しています。
`obj.__proto__`は非標準のプロパティです。動作しないブラウザがあります

### writable,configurableについての説明
`writable`を`true`にしなければ次に値を代入しても無視され変更する事はできません。つまり読取専用です。
`configurable`を`true`にすると、同じ名称のプロパティを再定義する事が出来ます

### proto !== Object.prototype とは？
forIn2の条件式で`proto !== Object.prototype`と有るのは`Object`の非列挙キーが表示されてしまいます。
結果がむごい事になるので便宜的に対象外にしています