【JavaScript 】new呼び出し、「this instanceof fNOP」の意味。
■topic summary
study to understand this site "Polyfill" section
Function.prototype.bind() - JavaScript | MDN
事の発端
パーフェクトJavaScript (PERFECT SERIES 4)
- 作者: 井上誠一郎,土江拓郎,浜辺将太
- 出版社/メーカー: 技術評論社
- 発売日: 2011/09/23
- メディア: 大型本
- 購入: 24人 クリック: 588回
- この商品を含むブログ (12件) を見る
bindはapplyもしくはcallを使えば独自に実装可能です
とあった。
ふむふむ。そうなんだ。
Mozillaのリファレンスにも
「bindが定義されていなかったらこのコードを使って代用してね」
とコードが載っている。
The bind function is a recent addition to ECMA-262, 5th edition; as such it may not be present in all browsers. You can partially work around this by inserting the following code at the beginning of your scripts,
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
ならやってみよう。
できるかな。
・・・から始まりました。
そして2週間くらい嵌っていました。。。
ここで明らかにすること。
fNOP = function () {}, fBound = function () { return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound;
このコードが何をやっているのか理解する。
・どういう時に「this instanceof fNOP」がtrue?
・fBoundのプロトタイプにfNOPのインスタンスを代入?
など。
まずは基本。
ケース1:new 呼び出しのthisコンテキスト
生成されるインスタンス自身が「this」にsetされます。
http://qiita.com/Haru39/items/9935ce476a17d6258e27
具体的に書いてみると、
Foo = function(){ console.log(this); } Foo.prototype.arg="ppp"; x = new Foo(); y = Foo();
■実行結果
Foo {arg: "ppp"} Window {top: Window, window: Window, location: Location, Proxy: Object, external: Object…}
(ブラウザで試行しているため、2個目はグローバルオブジェクトとしてwindowオブジェクトが返ってきている)
ケース2:new呼び出し時の返り値
returnで関数を返した場合は、その関数が返り値となる。
関数以外であればインスタンスが返る。
Goo = function(){ return "hoge"; } new Goo()
Foo = function(){ fn = function(){console.log("jjj")}; return fn; }; new Foo() ||< ■実行結果 >|| Goo {} function (){console.log("jjj")}
If the constructor function doesn't explicitly return an object, the object created in step 1 is used instead.
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/new
うーん。Stringもobjectだと思うから、この解説は良く分からないけど。。
本題
ここからが難しくなるところ!
まず、クラス(prototypeプロパティを持つオブジェクト)を返す関数を考える。
この関数をnew呼び出ししたときは、
thisコンテキストが生成されるインスタンスとなるので(∵ケース1)、
protorypeで定義したプロパティを読み取ることができる。
hoge = function () { fBound = function(){ return console.log(this.arg); }; fBound.prototype.arg = "ppp"; return fBound; }; boo = hoge(); x = new boo();
■実行結果
ppp
new呼び出しかどうかを判別する
注)ここからは憶測も入っているので確証ではないです。
探しても同種の内容に言及している資料が見つかりませんでした。
すみません。
自分が分からなかったのは、この
this instanceof fNOP、がtrueになるケース。
だってメソッド呼び出しできないわけだから、
thisは関数呼び出しの時のグローバルオブジェクトになるのでは?、
と思っていたわけです。
コンストラクタ呼び出しのときはthisがその返されるインスタンスになる。
このケースが頭になかったのです。
hoge = function (oThis) { var fNOP = function() {}, fBound = function() { return console.log(this instanceof fNOP); #<= ア }; fBound.prototype = new fNOP(); #<= イ return fBound; }; foo = hoge(); x = new foo(); #<= ウ
■実行結果
true
順に追っていくと、
hoge関数で返却された関数(クラス)をnew呼び出しすると、、
1)ウにてnew呼び出しをするため、
アのthisはfBoundオブジェクトとなる(∵ケース1)。
2)イにて、fBoundはプロトタイプに
fNOP.prototypeを継承したオブジェクトを持つ(※2)
3)(1)(2)より、thisのプロトタイプチェインでfNOP.prototypeを持つため、
アのinstanceofはtrueとなる(※3)
※2
When the code new foo(...) is executed, the following things happen:
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/new
A new object is created, inheriting from foo.prototype.
※3
instanceof 演算子は、object のプロトタイプチェインで constructor.prototype の存在を確認します。
https://developer.mozilla.org/ja/docs/JavaScript/Reference/Operators/instanceof
あらためて、事の発端となったMozillaのコードを見返してみると、、
Function.prototype.bind() - JavaScript | MDN
fBound = function () { return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, #<= (ア) aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound;
(ア)の三項演算子は、
関数呼び出し(this instanceof fNOPがfalse)⇒第一引数がoThis(bindの第一引数)
new呼び出し(this instanceof fNOPがtrue)⇒第一引数が生成されるインスタンス
という違いを表現している。
new呼び出しをしてもreturnで指定したapply関数の評価結果が返る(∵ケース2)
実際に書いてみると、
hoge = function(){console.log("-----");console.log(this)}; x = {age: 10}; fn = hoge.bind(x); fn(); new fn();
■実行結果
----- Object {age: 10} ----- fBound {}
fn()では thisがx
new fn()では thisがfBoundインスタンス。
何がうれしいの?
fBoundインスタンスだと何がうれしいのか。
fBoundクラスは内部に隠蔽されているわけだし?
答えは
fNOP.prototype = this.prototype; fBound.prototype = new fNOP();
のところ。
fBoundは、thisのプロトタイプを継承している。
つまり、thisのプロトタイプを定義してみると、
hoge = function(){console.log("-----");console.log(this)}; hoge.prototype.arg1 = "hello"; x={age: 10}; fn = hoge.bind(x); fn(); new fn();
■実行結果
----- Object {age: 10} ----- fBound {arg1: "hello"}
new呼び出しのときは、プロトタイプを継承している!
以上を、
・プロトタイプを持っているものをクラスとみなして先頭大文字にする
というルールで見やすく整理すると
Hoge = function(){console.log(this.age)}; Hoge.prototype.age = 999; x={age: 10}; fn = Hoge.bind(x); fn(); new fn();
■実行結果
10 999
どうだ!
まとめ
まとめると、
bindはthisを第一引数に差し替える関数だが、
bindの返り値をnew呼び出しすると、その法則が無視されて、
thisはバインドのレシーバーオブジェクトのインスタンスに差し替えられる。
。。。。つかれたぶひー。