こんにちは、@nazomikanです
この記事は(JavaScript - Client Side - Advent Calendar 2013)の15日目の記事です。
@teramako 妙な古臭さを感じるのだが…。 http://d.hatena.ne.jp/teramako/20131216/p1
と的確な斧をいただいたので記事を修正しています
これまで標準化されていなかった__proto__がES6で標準化されようとしています。
(大変古かったので差し替えました。)
参考: Object.prototype.__proto__
※ まだドラフトで確定ではないのでご注意ください。
Object.prototype.__proto__Enumerable: false
Configurable: true
内部変数のUnderscoreProtoEnabledの真偽によってこのプロパティの操作を追跡するかしないかを決定します。
UnderscoreProtoEnabledはObject.prototpyeをもっていればデフォルトでtrueになります。
重要なのは__proto__はObject.prototype上にいると明言されるようになったこと
と、__proto__のUnderscoreProtoEnabledの挙動が明言されたこと。
(Rev20 Oct 28 2013の時点でUnderscoreProtoEnabledの内部プロパティに関する記述が消えてました)
元となるのはこの話
__proto__標準化の背景
我らが唯一神のDouglasのGood Partsにこんなコードがあった。
that.on = function (type, ....) {
....
var registry = {};
....
if (registry.hasOwnProperty(type)) {
registry[type].push(handler);
} else {
registry[type] = [handler];
}
....
};
なんの変哲もないコードのように見えるがこのコードは本当の意味でセーフティではないのだ。
いちゃもんをつけるような話になりかねないのだが、例えばなんらかのモンキーパッチ(Object.prototypeへの変更を加えたりとか)によってregistryがhasOwnPropertyを持っていない可能性もある。
これはhasOwnPropertyをuncurryThisで初期時に束縛していれば解決できる。
var uncurryThis = Function.prototype.bind.bind(Function.prototype.call)
, hasOwn = uncurryThis(Object.prototype.hasOwnProperty)
;
//...
that.on = function (type, ....) {
....
var registry = {};
....
if (hasOwn(registry, type)) {
registry[type].push(handler);
} else {
registry[type] = [handler];
}
....
};
...
はじめて見る人は面食らうかもしれないがちょっと前に(ここ)http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming で流行った安全なコードだ。
付録的に末尾に書いといたので興味があれば見てほしい。
とりあえず、uncurryThisを用いたことによって、たとえregistryがモンキーパッチやObject.create(null)でhasOwnPropertyをもっていなかったとしても安全に動作するように変更できた。
これでこのコードは安全になったように見える。
しかし問題はまだあった。
ここからが本題で、typeという変数が関数の外から渡されてきていることに注目したい。
このtypeはhasOwnPropertyによってのみバリデーションをされている。
そう、ここが論点なのだ。
話の流れ的に分かるかもしれないがtypeが__proto__という文字列だったときに問題が起きる。
これまで__proto__は特に標準化もしていなかったためにそれぞれのブラウザ間で以下のような場合に評価結果が異なってくるのだ。
[{}.hasOwnProperty('__proto__'), {}.__proto__.hasOwnProperty('__proto__')]
false,false // Chrome true,true // WebKit Nightly false,true // FF Nightly false,false // Opera 12 alpha throws TypeError // IE10 Preview 2
(2011/12/26の時の話です)
IEが__proto__をサポートしてないがためにexceptionを発生させるが、そんなことは問題ではない。
ここで驚くのはWebKit Nightlyの挙動でしょう。
{}.hasOwnProperty('__proto__')の挙動がこのブラウザでだけtrueを指し示している。
最初のコードをもう一度見直してみよう
that.on = function (type, ....) {
....
var registry = {};
....
if (registry.hasOwnProperty(type)) { //....(1)
registry[type].push(handler); // ....(2)
} else {
registry[type] = [handler];
}
....
};
先ほどの実行結果を元に考えるとtypeの値が__proto__という文字列だった場合、(1)は
if (registry.hasOwnProperty('__proto__')) {
となり、Webkit Nightlyでのみtrueを返すことになる。
これによって(1)を通過し、(2)を実行する時に
registry['__proto__'].push(handler);
となり、__proto__はobjectなのでObject.prototype.pushなんてもってないよとばかりにエラーを発生させるのだ。
とまぁ、こんなブラウザ間での挙動が引き起こした穴を埋めるため標準化が始まりました。
今後(といっても確定ではないが)、__proto__はObject.prototype上にいると定義されるわけなので
[{}.hasOwnProperty('__proto__'), {}.__proto__.hasOwnProperty('__proto__')]
の実行結果は[false, true]に統一されていくのでしょう。
setPrototypeOf
ここまで__proto__標準化の流れについて色々書きましたがES6からはObject.setPrototypeOfが定義されつつあるので__proto__系の操作はそちらを通して行われるほうが今後としてはよいようです。
@teramakoさんまさかりありがとうございました(^-^)/
おまけ(uncurryThis)
以外と難しいので説明
obj.func1
func1においてthisの指す値はobjになる。 そう、つまりこの形においてthisとは.より左の部分を指す
つまり
var uncurryThis = Function.prototype.bind.bind(Function.prototype.call)
は
Function.prototype.bindのthisをFunction.prototype.callに変更している(= .より前を差し替えている)という風に言えるので
Function.prototype.call.bind
と等価だと言える。
さらにそれをふまえた上で
var uncurryThis = Function.prototype.bind.bind(Function.prototype.call),
hasOwn = uncurryThis(Object.prototype.hasOwnProperty);
if (hasOwn(registry, type))
を見てみると
var uncurryThis = Function.prototype.bind.bind(Function.prototype.call),
// === Function.prototype.call.bind
hasOwn = uncurryThis(Object.prototype.hasOwnProperty);
// === Function.prototype.call.bind(Object.prototype.hasOwnProperty)
// === Object.prototype.hasOwnProperty.call
if (hasOwn(registry, type))
// === if (Object.prototype.hasOwnProperty.call(registry, this))
と等価であることがわかる。
こんな感じでcallのthisを束縛してモンキーパッチに対抗するみたいな方法のことです。
株式会社ネクストでは、一緒に世の中をもっと便利に楽しくしたい仲間を募集中です。 主体的に楽しみながら仕事できる方からのエントリーお待ちしています! http://recruit.next-group.jp/