[JavaScript] Objectを辞書代わりに使ってはまるパターン

# Objectを辞書代わりに使ってはまるパターン

みなさんはよく以下の様なコードを書いたりしませんか。

```js
var dic = {};
dic['apple']  = {name: 'Apple'};
dic['orange'] = {name: 'Orange'};
```

使うよね。
この時、注意しないといけない事があるんだよね。

知ってるって? でも、まぁ、一応読んでみてくださいよ。

# キーの一覧を配列で取得する

これは、普通 OK だと思う。

```js
console.log(Object.keys(dic));
// -> [ 'apple', 'orange' ]
```

# for in でキーを使ってループする

これも、まぁ、大体は OK だと思う。

```js
for (var i in dic)
  console.log(i, dic[i]);
// -> apple { name: 'Apple' }
// -> orange { name: 'Orange' }
```

※後述の `__proto__` に注意

# in を使ってキーが存在するかどうかチェックする

これも、よく使うかな。
apple, orange はあるけど grape は無いね。

```js
console.log('apple ', 'apple'  in dic);  // -> true
console.log('orange', 'orange' in dic);  // -> true
console.log('grape ', 'grape'  in dic);  // -> false
```

※やはり `__proto__` に注意

# [キー] で存在チェックする

存在チェックしたり、ターゲットのオブジェクトにアクセスするよね。

```js
console.log('apple ', dic['apple']  ? true : false);  // -> true
console.log('orange', dic['orange'] ? true : false);  // -> true
console.log('grape ', dic['grape']  ? true : false);  // -> false
```

※しつこく `__proto__` ね

# Object にデフォルトのメソッドやプロパティが...

そう言えば、JavaScript の Object にはデフォルトのメソッドやプロパティがあるよね。
注意するには... あ、そうそう、 `hasOwnProperty` を使うんだよね。

以下は全部期待通りだ。

```js
console.log('apple ', dic.hasOwnProperty('apple'));   // -> true
console.log('orange', dic.hasOwnProperty('orange'));  // -> true
console.log('grape ', dic.hasOwnProperty('grape'));   // -> false
```

以下は全部 false だ。

```js
console.log('constructor   ', dic.hasOwnProperty('constructor'));
console.log('__proto__     ', dic.hasOwnProperty('__proto__'));
console.log('toString      ', dic.hasOwnProperty('toString'));
console.log('hasOwnProperty', dic.hasOwnProperty('hasOwnProperty'));
```

ちょっと待てよ。
じゃ、これらをキーに使ったらどうなるんだろうねぇ。

```js
dic['constructor']    = {name: 'constructor'};
dic['__proto__']      = {name: '__proto__', grape: true};
dic['toString']       = {name: 'toString'};
dic['hasOwnProperty'] = {name: 'hasOwnProperty'};
```

あ、なんか壊れた。ダメだ。
`'grape'` とか `'name'` とか辞書に入っている様に見える。

※実行結果はまとめのソースを見てね

そもそも `{}` を使ったのがダメだったのかな。

# Object.create(null) で被害を最小限に食い止める

```js
var dic = Object.create(null);
dic['apple']     = {name: 'Apple'};
dic['orange']    = {name: 'Orange'};
dic['__proto__'] = {name: '__proto__', grape: true};
```

これだと、そもそも dic.hasOwnProperty すら無い。
結構良さそう。

でも、キー `__proto__` だけは、問題が残るんだよね。
やっぱり、これだけは辞書に入れられない。

# 最後の手段、キーには重ならない様な文字を追加する

もう、あきらめの境地だ。
先頭に `!` でも追加してみるか。

```js
dic['!apple']     = {name: 'Apple'};
dic['!orange']    = {name: 'Orange'};
dic['!__proto__'] = {name: '__proto__', grape: true};
```

やっと、うまく行きそう。

誰か、もっといい方法があれば、教えてください。

# 最終兵器 ES6 の Map/Set を使う

クライアント(ブラウザ)は、あきらめよう。
最終兵器だ。
`node --harmony` (`--harmony-collections`) で `Map` や `Set` を使うのだ。

`size`/`has`/`delete`/`clear`/`get`/`set`(`add`) などがある。
`Map`/`Set`/`WeakMap`/`WeakSet` かなり便利そうだ。

# 自分で作るか npm 等で探してくる

まぁ、自分で作れば良いかな。
作るより探してくれば良いかな。

npm には es6-map とか、あるみたいだし、探せば出てきそうだな。

# まとめ

1. `{}` では `hasOwnProperty` を使うなど結構注意しないといけない
2. `Object.create(null)` でも `__proto__` と `constructor` 等は注意
3. あきらめて、キーには何か特殊な prefix を付けておく
4. 最終兵器 ES6 `Map`/`Set` クラスを使う
5. 自分で `Map`/`Set` クラスを作るか npm es6-map などを使う

くらいでしょうか。

# テスト・プログラム

一度にテストするプログラムを貼っておきます。

```js
'use strict';

var dic = {};
checkDictionary(dic, '{}');

dic = Object.create(null);
checkDictionary(dic, 'Object.create(null)');


function checkDictionary(dic, desc) {
  dic['apple']          = {name: 'Apple'};
  dic['orange']         = {name: 'Orange'};
  dic['constructor']    = {name: 'constructor'};
  dic['__proto__']      = {name: '__proto__', grape: true};
  dic['toString']       = {name: 'toString'};
  dic['hasOwnProperty'] = {name: 'hasOwnProperty'};

  console.log("***", desc, "*** Object.keys(dic) ***");
  console.log(Object.keys(dic));
  // -> [ 'apple', 'orange', 'constructor', 'toString', 'hasOwnProperty' ]
  // ★__proto__ はどこに行った?
  console.log();

  console.log("***", desc, "*** for (var i in dic) console.log(i, dic[i]); ***");
  for (var i in dic) console.log(i, dic[i]);
  // -> apple { name: 'Apple' }
  // -> orange { name: 'Orange' }
  // -> constructor { name: 'constructor' }
  // -> toString { name: 'toString' }
  // -> hasOwnProperty { name: 'hasOwnProperty' }
  // -> name __proto__
  // -> grape true
  // ★__proto__ は無いし、name とか grape って何?
  console.log();

  console.log("***", desc, "*** 'key' in dic ***");
  console.log('apple ', 'apple' in dic);                // -> true
  console.log('orange', 'orange' in dic);               // -> true
  console.log('grape ', 'grape' in dic);                // -> true ★変
  console.log();

  console.log("***", desc, "*** dic['key'] ? true : false ***");
  console.log('apple ', dic['apple']  ? true : false);  // -> true
  console.log('orange', dic['orange'] ? true : false);  // -> true
  console.log('grape ', dic['grape']  ? true : false);  // -> true ★変
  console.log();

  if (typeof dic.hasOwnProperty === 'function') {
    console.log("***", desc, "*** dic.hasOwnProperty('key') ***");
    console.log('apple ', dic.hasOwnProperty('apple'));   // -> true
    console.log('orange', dic.hasOwnProperty('orange'));  // -> true
    console.log('grape ', dic.hasOwnProperty('grape'));   // -> false
    console.log();
  }

  console.log("***", desc, "*** 'special-key' in dic ***");
  console.log('constructor   ', 'constructor' in dic);     // -> true
  console.log('__proto__     ', '__proto__' in dic);       // -> true
  console.log('toString      ', 'toString' in dic);        // -> true
  console.log('hasOwnProperty', 'hasOwnProperty' in dic);  // -> true
  console.log();

  console.log("***", desc, "*** dic['special-key'] ? true : false ***");
  console.log('constructor   ', dic['constructor'] ? true : false);    // -> true
  console.log('__proto__     ', dic['__proto__']   ? true : false);    // -> true
  console.log('toString      ', dic['toString']    ? true : false);    // -> true
  console.log('hasOwnProperty', dic['hasOwnProperty'] ? true : false); // -> true
  console.log();

  if (typeof dic.hasOwnProperty === 'function') {
    console.log("***", desc, "*** dic.hasOwnProperty('special-key') ***");
    console.log('constructor   ', dic.hasOwnProperty('constructor'));     // -> false
    console.log('__proto__     ', dic.hasOwnProperty('__proto__'));       // -> false
    console.log('toString      ', dic.hasOwnProperty('toString'));        // -> false
    console.log('hasOwnProperty', dic.hasOwnProperty('hasOwnProperty'));  // -> false
    console.log();
  }
}
```

# 参考文献

オブジェクトの `__proto__` 等がなぜ使用してはいけないキーなのかは、以下の資料が参考になると思います。
(Firefox, Chrome/Node.js/v8 系の JavaScript での問題)

[[JavaScript] そんな継承はイヤだ - クラス定義 - オブジェクト作成]
(http://qiita.com/LightSpeedC/items/d307d809ecf2710bd957)