Javascriptにおけるthisバインディングの3つの方法と比較

9078 ワード

紹介する
thisはjavascriptの中で最も味わい深い特性と言えます.thisを学ぶ第一歩は、関数自体でも関数にも向かないスコープを指すことでもないことが分かります.thisは実際に関数が呼び出された時に起こる結合で、どこを指すかは関数がどこで呼び出されるかによって完全に異なります.
なぜバインディングが必要ですか?
thisは現在のコンテキスト環境を指しています.

var info = "This is global info";
var obj = {
 info: 'This is local info',
 getInfo: getInfo
}
function getInfo() {
 console.log(this.info);
}
obj.getInfo() //This is local info

getInfo() //This is global info            
上記の例では、オブジェクト内部に属性getInfoを作成し、グローバルスコープにおけるgetInfoを参照するとともに、現在のコンテキストにおけるinfoの値を印刷する役割を果たし、obj.getInfoを使用して呼び出しを行うと、オブジェクト内部のinfoの値が印刷され、このときthisはオブジェクトを指す.グローバルの関数を使うと、グローバル環境におけるinfo変数の値が印刷されます.このとき、thisはグローバルオブジェクトを指します.
この例は私達に教えてくれます.
     1、同じ関数では、呼び方が違って、thisの指し方が違って、結果が違ってきます.
     2、オブジェクト内部の属性の値が参照タイプの場合、thisの指向は元のオブジェクトにずっと結合されません.
次に、うっかりしてthisがなくなった場合があります.

var info = "This is global info";
var obj = {
 info: 'This is local info',
 getInfo: function getInfo() {
 console.log(this.info);

 var getInfo2 = function getInfo2() {
  console.log(this.info);
 }
 getInfo2();
 }
}
obj.getInfo();

//This is local info
//This is global info
上記の例では、オブジェクトobjには、新しい関数が定義されています.また、最外層のオブジェクトのinfo属性の値を取得したいですが、関数内関数のthisが誤ってwindowグローバルオブジェクトの上に向けられています.
解決の方法も簡単で、最外層に変数を定義して、現在の語法の作用領域内のthisの指す位置を記憶して、変数の作用領域の関係によって、その後の関数内部はさらにこの変数にアクセスして、上層関数内部のthisの真の指向を得ます.

var info = "This is global info";
var obj = {
 info: 'This is local info',
 getInfo: function getInfo() {
 console.log(this.info);

 var self = this;  //   this      
 var getInfo2 = function getInfo2() {
  console.log(self.info); //         this
 }
 getInfo2();
 }
}
obj.getInfo();

//This is local info
//This is local info
しかし、上のself変数はobjオブジェクトを再参照したものに等しいという問題もあります.このようにすると、ある時、うっかりオブジェクト全体を修正してしまうかもしれません.また、複数の環境下のthis指向を取得する必要がある場合、複数の変数を宣言して管理に不利です.
いくつかの方法があります.Selfのような変数を宣言しない条件で、現在の環境下のコンテキストを結合して、プログラム内容の安全を確保することができます.
どのようにthisを結合しますか
1.call、appy
callとapplyはFunction.prototypeに定義されている関数の二つです.彼らの役割は修正関数が実行するコンテキスト、すなわちthisの指向問題です.
callを例にとって、上記のanother Funはlocal thingを出力したいです.このように修正します.

...
var anotherFun = obj.getInfo;
anotherFun.call(obj) //This is local info
関数呼び出しのパラメータ:
      Function.prototype.call(context [, argument1, argument2 ])
      Function.prototype.apply(context [, [ arguments ] ])
ここから分かるように、callとappyの第一パラメータは必須であり、新たに修正された文脈を受けて、第二のパラメータはいずれもオプションであり、callは第二のパラメータから始まり、着信呼び出し関数を受け入れる値は単独で出現し、appyは1つの配列から導入されるという違いがあります.

function add(num1, num2, num3) {
 return num1 + num2 + num3;
}
add.call(null, 10, 20, 30); //60
add.apply(null, [10, 20, 30]); //60
受信したcontextがundefinedまたはnullの場合、自動的にグローバルオブジェクトに修正されます.上記の例はwindowです.
2.Funtions.prototype.bindでバインディングする
ES 5にFunction.prototypebind方法が追加されました.バインディングが必要なコンテキストオブジェクトを受け入れ、呼び出し関数のコピーを返します.同様に、パラメータを後に追加して関数のコリック化を実現してもいいです.

Function.prototype.bind(context[, argument1, argument2])
//       
function add(num1 ,num2) {
 return num1 + num2;
}
var anotherFun = window.add.bind(window, 10);
anotherFun(20); //30
同時に、彼は関数のコピーを返し、関数を着信文脈に永遠に結びつける.

...
var anotherFun = obj.getInfo.bind(obj)
anotherFun(); //This is local info
polyfill
polyfillは、下の互換性のための解決策であり、ES 5に対応していない古いブラウザで、どのようにbindメソッドを使うかについては、古い方法でbindメソッドを書き換える必要があります.

if (!Function.prototype.bind) {
 Function.prototype.bind = function (obj) {
 var self = this;
 return function () {
 self.call(obj);
 }
 }
}
上記の表記は関数を返し、文脈をパラメータに修正しましたが、コリゼーション部分は実現されていません.

...
Function.prototype.bind = function(obj) {
 var args = Array.prototype.slice.call(arguments, 1); 
 //             

 var self = this;
 return function () {
 self.apply(obj, args.concat(Array.prototype.slice.call(arguments)));
 }
}
bindを使用してバインディングした後、再度callによる修正ができないので、bindバインディングは硬いバインディングとも呼ばれます.
3.newキーワードでバインディングする
jsでは、関数には直接的に呼び出しを行い、newキーワードを通じて構造呼び出しを行う2つの呼び出しがあります.

function fun(){console.log("function called")}
//    
fun() //function called
//    
var obj = new fun() //function called
普通の呼び出しとnewキーワードを使った構造呼び出しの間には、どのような違いがありますか?
正確には、newキーワードは呼び出し関数に基づいて、いくつかのステップを追加しただけで、修正thisポインタがreturnに戻るオブジェクトに含まれています.

var a = 5;
function Fun() {
 this.a = 10;
}
var obj = new Fun();
obj.a //10
いくつかのバインディングの優先度の比較
以下の例では、いくつかのバインディング状態の優先度の重みの比較を行う.

var obj1 = {
 info: "this is obj1",
 getInfo: () => console.log(this.info)
}

var obj2 = {
 info: "this is obj2",
 getInfo: () => console.log(this.info)
}
1.call、appyとデフォルト指向の比較
まず、使用頻度によっては、コールとapplyの方がダイレクトコールより優先度が高いことは明らかです.

obj1.getInfo() //this is obj1
obj2.getInfo() //this is obj2
obj1.getInfo.call(obj2) //this is obj2
obj2.getInfo.call(obj1) //this is obj1
callを使うのはappyと比べるとnewを使うのですか?
この時問題が発生します.new function.call(something)のようなコードは実行できません.したがって、私たちはbind法で新しい関数を返して、newで優先度を判断します.

var obj = {}
function foo(num){
 this.num = num;
}

var setNum = foo.bind(obj);
setNum(10);
obj.num //10

var obj2 = new setNum(20);
obj.num //10
obj2.num //20
この例では、newを使って構造呼び出しを行うと、新しいオブジェクトに戻り、このオブジェクトにthisを修正しますが、これまでのオブジェクトの内容は変わりません.
問題が来ました.上に書いたビッドのpolyfillは明らかにこのような能力を備えていません.MDNでは、ビッドのpolyfill方法があり、その方法は以下の通りである.

if (!Function.prototype.bind) { 
 Function.prototype.bind = function (oThis) { 
 if (typeof this !== "function") { 
 throw new TypeError("Function.prototype.bind - 
 what is trying to be bound is not callable"); 
 }
 var aArgs = Array.prototype.slice.call(arguments, 1),
 fToBind = this, 
 fNOP = function () {}, 
 fBound = function () { 
  return fToBind.apply(this instanceof fNOP ? 
    this : oThis || this, 
   aArgs.concat(Array.prototype.slice.call(arguments))); 
 }; 
 fNOP.prototype = this.prototype; 
 fBound.prototype = new fNOP(); 
 return fBound; 
 };
}
上記のpolyfillは、まずバインディングが必要なオブジェクトが関数かどうかを判断し、Function.prototype.bind.call(something)の使用を防止する場合、somethingは関数ではなく未知のエラーを引き起こす.その後、戻りたいfBound関数をthisから引き継ぎ、fBoundに戻ります.
特別な事情
もちろん、場合によっては、thisの指針にもいくつかの意外性があります.
矢印関数
ES 6には、関数を定義する方式が追加されています.「=」を使って関数の定義を行います.その内部では、thisのポインタは変化せず、常に最外層の語法作用領域を指します.

var obj = {
 num: 1,
 getNum: function () {
 return function () {
 //this  
 console.log(this.num); 
 //   this  window
 }
 }
}
obj.getNum()(); //undefined

var obj2 = {
 num: 2,
 getNum: function () {
 return () => console.log(this.num); 
 //          getNum this,  this       
 }
}
obj2.getNum()(); //2
ソフトバインディング
上で提供したbind方法は、this指向を強制的に修正することにより、call、appyによる修正ができません.もし私たちがbind効果があることを望むならば、callとappyによって関数を二回修正することもできます.この時にFunction.prototypeに確立された方法を書き換える必要があります.「ソフトバインディング」と名づけます.

if (!Function.prototype.softBind) {
 Function.prototype.softbind = function (obj) {
 var self = this;
 var args = Array.prototype.slice.call(arguments, 1);
 return function () {
 return self.apply((!this || this === (window || global)) ? 
   obj : this, 
   args.concat(Array.prototype.slice.call(arguments)));
 }
 }
}
bind、callの効用
平日には、擬似配列要素を通常の配列要素に変更する必要があります.上記の例のように、Array.prototype.slice方法によってしばしば利用されます.argmentsというオブジェクトを真の配列オブジェクトに変換し、Array.prototype.slice.call(arguments)を用いて変換します.しかし、この方法を使うたびには長すぎて、煩雑です.だから時々私たちはこう書きます.

var slice = Array.prototype.slice;
slice(arguments);
//error
同じ問題がまた発生します.

var qsa = document.querySelectorAll;
qsa(something);
//error
上記の問題は、sliceとquerySelectorAllを内蔵しています.内部ではthisを使用しています.簡単に引用すると、thisは運行中にグローバル環境windowになります.もちろん間違いが発生します.私たちはbindを簡単に使うだけで、関数のコピーを作成できます.

var qsa = document.querySelectorAll.bind(document);
qsa(something);
同様に、callとappyも関数であるので、それらの上でbindメソッドを呼び出すこともできます.このようにして、戻ってきた関数のコピー自体に修正ポインタの機能が付いています.

var slice = Function.prototype.call.bind(Array.prototype.slice);
slice(arguments);
締め括りをつける
以上はこの文章の全部の内容です.本文の内容は皆さんの学習や仕事に一定の助けをもたらしてくれると思います.