36
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-06-25

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

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

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

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

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

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

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

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

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

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

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

※後述の __proto__ に注意

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

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

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

※やはり __proto__ に注意

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

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

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 を使うんだよね。

以下は全部期待通りだ。

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

以下は全部 false だ。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 などを使う

くらいでしょうか。

テスト・プログラム

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

'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)

36
33
9

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?