{__proto__:null} のススメ

特に新しい話題でもないけど,使われているところをあまり見ない気がするので。

HashMap/Dictionary として

narcissus/jsdefs.js#142 にもあるように,単に {} だと Object.prototype が付いて廻るので役に立たないことがある。辞書として使うなら {__proto__:null} にしておいて損はない。

速度稼ぎ

辿る親が居ないのでプロパティアクセスは少し速い。はず。

if(this.dump) print = function(x){ dump(x +'\n') };
if(this.console) print = function(x){ console.log(x) };
function say(){ print('|'+ [].join.call(arguments, '|') +'|') }
['{}', '{__proto__:null}'].map(function(p){
  say('*<code>'+ p +'</code>', '*get', '*spy', '*pry');
  var tests = ['_._', '"_" in _', 'for(var $ in _) $'];
  var sents = {absent : '', present: '_._ = _;'};
  for(var s in sents) say.apply(0, [s].concat(tests.map(function(code){
    return Function(
      'var _ = '+ p +', i = 1e4, t = new Date;'+ sents[s] +
      (p[2] ? 'if("__proto__" in _) _.__proto__ = null;' : '') + // for Rhino
      'do {'+ Array(51).join(code +';') +'} while(--i);'+
      'return new Date - t')();
  })));
});
{} get: . [] spy: in pry: for-in
absent (新規) 19 123 416
present (上書き) 4 34 460
{__proto__:null} get spy pry
absent 5 27 308
present 4 30 401
{} get spy pry
absent 687 839 512
present 108 144 889
{__proto__:null} get spy pry
absent 150 172 692
present 117 141 1004
{} get spy pry
absent 1203 86 48
present 5 70 60
{__proto__:null} get spy pry
absent 1170 61 168
present 5 71 39
  • Nitro (Safari 4.0.4/531.21.10)
{} get spy pry
absent 78 108 274
present 2 56 282
{__proto__:null} get spy pry
absent 21 54 268
present 2 56 283

実装によって傾向が違うが概ね速くなる。for in が逆に遅かったりするのは謎。

?. もどき

nullundefined かもしれない変数のプロパティを確かめたいとき,直に参照すると例外を喰らうためそれを避けて

try { x = hoge.fuga.piyo } catch(e if e instanceof TypeError){ x = void 0 }

x = hoge != null && hoge.fuga != null ? hoge.fuga.piyo : void 0;

こんな風に書かざるを得ない。
そんな風に書きたくはないので,0 をほとんど空と見なして

x = ((hoge || 0).fuga || 0).piyo;

と横着することがままある。Groovy の [http://groovy.codehaus.org/Operators#Operators-SafeNavigationOperator%28%3F.%29:title=?.] ほど綺麗にならないが許容範囲。((false 相当を軒並み弾いてしまうのが難ではある))
ただ 0Number.prototype にも Object.prototype にも影響を受けてしまう。まともな人々はこの辺を弄ったりしないが,世の中にはまともでない人々も居たりするので

const Z = {__proto__: null};

x = ((hoge || Z).fuga || Z).piyo;

こうしてみる。Z は真空*1なので安心である。

互換性
var Z = {__proto__:null};
Z.__proto__ && (Z.__proto__ = null);
['__proto__' in Z, '__proto__' in {}, Z.__proto__]
TraceMonkey Rhino v8 Nitro
'__proto__' in Z false true true true
'__proto__' in {} true false true true
Z.__proto__ void 0 null null null
TraceMonkey
__proto__ が跡形も無い。(→ 辞書として完全)
Rhino
後から代入しないと親を null にできないが,逆に代入しないと in から __proto__ が見えない。
問題点 (SpiderMonkey 限定)
  • __count__ できない。
({}).__count__ // 0
({__proto__: null}).__count__ // undefined
  • プロパティの有無にかかわらず uneval"null" を返す。
    (JSON.stringify() は OK)。
uneval({a: 1}) // '({a:1})'
uneval({__proto__:null, a: 1}); // 'null'
JSON.stringify({__proto__:null, a: 1}); // '{"a":1}'

*1:故意に何か入れない限り