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
) で Map
や Set
を使うのだ。
size
/has
/delete
/clear
/get
/set
(add
) などがある。
Map
/Set
/WeakMap
/WeakSet
かなり便利そうだ。
自分で作るか npm 等で探してくる
まぁ、自分で作れば良いかな。
作るより探してくれば良いかな。
npm には es6-map とか、あるみたいだし、探せば出てきそうだな。
まとめ
-
{}
ではhasOwnProperty
を使うなど結構注意しないといけない -
Object.create(null)
でも__proto__
とconstructor
等は注意 - あきらめて、キーには何か特殊な prefix を付けておく
- 最終兵器 ES6
Map
/Set
クラスを使う - 自分で
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)