こんにちは、@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/