せかいや

いまいるここを、おもしろく http://sekai-in-the-box.appspot.com/

【JavaScript 】new呼び出し、「this instanceof fNOP」の意味。

 
■topic summary
study to understand this site "Polyfill" section
Function.prototype.bind() - JavaScript | MDN




事の発端

 

パーフェクトJavaScript (PERFECT SERIES 4)

パーフェクトJavaScript (PERFECT SERIES 4)

読んでいる本の中に

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週間くらい嵌っていました。。。


 

ここで明らかにすること。

Mozillaに載っているコード

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:
A new object is created, inheriting from foo.prototype.

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/new

 
※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はバインドのレシーバーオブジェクトのインスタンスに差し替えられる。


 
。。。。つかれたぶひー。