JavaScriptの中のthisバインディングについて詳しく説明します.

9304 ワード

thisはjavascriptの中で最も味わい深い特徴と言えます.高校英語の様々な時制のように、受け身の時制、過去の時、過去の時と同じように、何回見逃しても、次の時には依然として間違いがあります.本論文は「あなたの知らないJavaScript上巻」にヒントを与え、javasriptの中のthisをまとめます.
thisを学習する第一歩は、thisが関数自体でも関数にも向かない作用域であることを知ることである.thisは実際に関数が呼び出された時に起こる結合で、どこを指すかは関数がどこで呼び出されるかによって完全に異なります.
標準バインディング
javascriptでは、最も一般的な関数呼び出しのタイプは独立した関数呼び出しであるため、この規則は他の規則が適用されない場合のデフォルトルールと見なされます.関数を呼び出した場合、関数には修飾がなく、すなわち「裸」の呼び出しがあると、デフォルトのバインディング規則が適用され、デフォルトのバインディングは大域的な作用領域を指します.

function sayLocation() {
 console.log(this.atWhere)
}

var atWhere = "I am in global"

sayLocation() //     ,this       ,   “I am in global”


もう一つの例を見ます

var name = "global"
function person() {
 console.log(this.name) // (1) "global"
  person.name = 'inside'
 function sayName() {
  console.log(this.name) // (2) "global"    "inside"
 }
 sayName() //  person      sayName  ,this           
}
person()
この例では、person関数はグローバルスコープで呼び出されるので、(1)文のthisはグローバルオブジェクトに結び付けられています.(ブラウザではwindowで、nodeではglobalです.)だから、第(1)文は自然に出力されるのはグローバルオブジェクトのname属性です.もちろん、globalです.sayName関数は、person関数内で呼び出されます.それでも、(2)文のthisは全体のオブジェクトを指します.たとえperson関数がname属性を設定していても.
これはデフォルトのバインディング規則であり、javascriptの中で最も一般的な関数呼び出しモードであり、thisのバインディングルールも四つのバインディング規則の中で最も簡単なもので、グローバルスコープの役割領域にバインドされています.
デフォルトのバインディングの厳密なモード
javascriptでは、厳密なモードを使用すると、thisはグローバルオブジェクトにバインドされない.初めての例ですが、今回は厳しいパターンを加えて声明します.

'use strict'
function sayLocation() {
 console.log(this.atWhere)
}
var atWhere = "I am in global"
sayLocation()
// Uncaught TypeError: Cannot read property 'atWhere' of undefined
厳密なモードでは、thisをグローバルオブジェクトに結びつけると、実際にはundefinedをバインドしていますので、上記のコードはエラーとなります.
陰式バインディング
関数が呼び出されたとき、関数にはいわゆる「ドロップポイント」があると、コンテキストオブジェクトがあると、関数のthisがこのコンテキストオブジェクトにバインドされます.上の話がはっきりしないと思ったら、やはりコードを見に来ます.

function say() {
 console.log(this.name)
}
var obj1 = {
 name: "zxt",
 say: say
}

var obj2 = {
 name: "zxt1",
 say: say
}
obj1.say() // zxt
obj2.say() // zxt1

簡単ですよね.上記のコードの中で、obj 1、obj 2はいわゆるsay関数の足掛かりであり、専門的な言い方はコンテキストオブジェクトであり、関数にこのコンテキストオブジェクトを指定すると、関数内部のthisは自然にこのコンテキストオブジェクトを指します.これも一般的な関数コールモードである.
陰でバインディングするとコンテキストが失われます.

function say() {
 console.log(this.name)
}
var name = "global"
var obj = {
 name: "inside",
 say: say
}
var alias = obj.say //        (1) 
alias() //        "global" (2)
ここで出力されているのが見えますが、「global」です.なぜ上記と違って、私たちはObj.sayの名前を変えただけですか?まず上の(1)の文のコードを見てみます.javascriptでは関数は対象であり、対象間は引用伝達であり、値伝達ではありません.だから、(1)文コードはalias=obj.say=sayだけです.つまりalias=say、obj.sayは橋の役割を果たしただけです.aliasは最終的にsay関数のアドレスを引用して、objというオブジェクトとは関係がないです.これはいわゆる「コンテキストを失う」ということです.最終的にはalias関数を実行しました.ただ簡単にsay関数を実行して、「global」を出力します.
明示的なバインディング
明示的な結合は、名前の通りにthisを一つの文脈に結びつけ、javascriptでは、3つの明示的な結合の方法、appy、call、bindを提供します.
apply(obj,arg 1,arg 2,arg 3,...)の関数を呼び出すパラメータは、行列の形でコール(obj,arg 1,arg 2,arg 3,...)を与えます.

//     
function speak() {
  console.log(this.name)
}

var name = "global"
var obj1 = {
  name: 'obj1'
}
var obj2 = {
  name: 'obj2'
}

speak() // global    speak.call(window)
speak.call(window)

speak.call(obj1) // obj1
speak.call(obj2) // obj2

したがって、applyは関数にコンテキストを実行し、明示的に結合させる役割を果たしていることが分かります.したがって、関数内のthisはコールまたはappyで呼び出されたオブジェクトの上に自然に結合されています.

//    
function count(num1, num2) {
  console.log(this.a * num1 + num2)
}

var obj1 = {
  a: 2
}
var obj2 = {
  a: 3
}

count.call(obj1, 1, 2) // 4
count.apply(obj1, [1, 2]) // 4

count.call(obj2, 1, 2) // 5
count.apply(obj2, [1, 2]) // 5

上記の例では、appyとcallの使い方の違いを説明しています.bind関数は、指定された実行コンテキストを結びつけた新しい関数を返します.それとも、上のコードを例にしますか?

//    
function count(num1, num2) {
  console.log(this.a * num1 + num2)
}

var obj1 = {
  a: 2
}

var bound1 = count.bind(obj1) //      
bound1(1, 2) // 4

var bound2 = count.bind(obj1, 1) //        
bound2(2) // 4

var bound3 = count.bind(obj1, 1, 2) //        
bound3() //4

var bound4 = count.bind(obj1, 1, 2, 3) //         ,         
bound4() // 4

したがって、bind法は新しい関数を返しただけで、この関数内のthisは実行コンテキストを指定し、この新しい関数を返してパラメータを受け入れることができます.
newバインディング
最後に言うthisバインディング規則とは、newオペレータを介してコンストラクタを呼び出す際に発生するthisバインディングのことです.まず明確にしたいのは、javascriptには他の言語のような類の概念がありません.コンストラクタは普通の関数だけです.コンストラクターの関数名は大文字で始まるだけです.newで操作できます.コールしただけです

function Person(name,age) {
  this.name = name
  this.age = age
  console.log("           ")
}
Person("zxt",22) // "           "
console.log(name) // "zxt"
console.log(age) // 22

var zxt = new Person("zxt",22) // "           "
console.log(zxt.name) // "zxt"
console.log(zxt.age) // 22

上記の例では、まずPerson関数が定義されています.普通に呼び出すこともできるし、構造関数として呼び出すこともできます.普通に呼び出すと、通常の関数で実行され、文字列が出力されます.一つのnewオペレータを通じて新しいオブジェクトが作成されます.次に、二つの呼び出し方法を見てみます.thisはそれぞれ結合されています.どこでまず一般的に起動するかについて前に紹介したように、デフォルトのバインディングルールを適用してグローバルオブジェクトにthisを結びつけると、グローバルオブジェクトにそれぞれnameとageの2つの属性が追加されます.newオペレータを介して呼び出した場合、関数はオブジェクトに戻り、出力結果からこのリターンされたオブジェクトにthisオブジェクトが紐付けされます.のnewバインディングとは、newオペレータを介して関数を呼び出すと、新しいオブジェクトが発生し、このオブジェクトにコンストラクタ内のthisをバインドします.実は、javascriptでは、newを使って関数を呼び出して、自動的に次の操作を行います.
  • 新たなオブジェクト
  • を作成します.
  • この新しいオブジェクトはプロトタイプ接続されます.
  • この新しいオブジェクトは、関数呼び出しのthis
  • に結び付けられます.
  • 関数が他のオブジェクトに戻っていない場合、new式の関数呼び出しは自動的にこの新しいオブジェクト
  • に戻ります.
    4つのバインディングの優先度
    javascriptの4つのthisバインディングルールは、基本的にすべての関数呼び出しの状況をカバーしています.しかし、この4つのルールを同時に適用すると、2つ以上のものがあります.どのような場合がいいですか?あるいは、この4つのバインディングの優先順位はどうなりますか?まず、デフォルトバインディングの優先度が一番低いということが分かりやすいです.これは他のthisバインディング規則が適用されない場合にのみ、デフォルトバインディングを呼び出すためです.隠しバインディングと明示的バインディングですか?それともコードを付けますか?コードは嘘をつかないことがあります.
    
    function speak() {
      console.log(this.name)
    }
    
    var obj1 = {
      name: 'obj1',
      speak: speak
    }
    var obj2 = {
      name: 'obj2'
    }
    
    obj1.speak() // obj1 (1)
    obj1.speak.call(obj2) // obj2 (2)
    
    
    上記のコードでは、Obj 1.speak()を実行し、speak関数内部のthisがObj 1を指していますので、(1)コード出力はもちろんObj 1ですが、明示的にspeak関数内のthisからObj 2に結び付けられた場合、出力結果はObj 2になります.この結果から、明示的なバインディングの優先度は陰的バインディングより高いことが分かります.実際には、Obj 1.speak.cal(obj 2)をこう理解することができます.この行のコードは、Obj 1.speakが間接的にspeak関数の参照を得ただけで、前に述べたような暗黙的なバインディングが文脈を失ってしまいます.
    
    function foo(something) {
      this.a = something
    }
    
    var obj1 = {}
    var bar = foo.bind(obj1) //        bar,       this   obj1 (1)
    bar(2) // this    Obj1 ,  obj1.a === 2
    console.log(obj1.a)
    
    var baz = new bar(3) //   new     ,bar   this         baz (2)
    
    console.log(obj1.a)
    console.log(baz.a) 
    
    
    (1)において、bar関数内部のthisは元々Obj 1を指していたが、(2)においてnewオペレータによって呼び出されたため、bar関数内部のthisは戻りの例を再び指し示しており、newバインディングの優先度が明示的なバインディングよりも高いことを示している.これにより、4つのバインディング規則の優先順位付けがそれぞれ得られた.
    newバインディング>明示的バインディング>陰的バインディング>標準バインディング
    矢印関数のthisバインディング
    矢印関数はES 6の重要な特性です.矢印関数のthisは外層(関数または大域)の作用領域によって決定されます.関数内のthisオブジェクトは、以前に紹介した呼び出し時に結合されたオブジェクトではなく、定義時に存在するオブジェクトを指します.例を挙げてください.
    
    var a = 1
    var foo = () => {
      console.log(this.a) //         ,  this        
    }
    
    var obj = {
      a: 2
    }
    foo() // 1 ,        
    foo.call(obj) // 1,    , obj     ,        
    
    
    上記の例から、矢印関数のthis強制的なバインディングは矢印関数定義時に存在する作用領域であり、また、appyやcall方法などのバインディングを表示することによっては変更できないことが分かりました.以下の例を見てみます.
    
    //         
    function Person(name,age) {
      this.name = name
      this.age = age 
      this.speak = function (){
        console.log(this.name)
        //     (     ),this          
      }
      this.bornYear = () => {
        //     2016 ,  new Date().getFullYear()    2016
        //     ,this       
        console.log(new Date().getFullYear() - this.age)
        }
      }
    }
    
    var zxt = new Person("zxt",22)
    
    zxt.speak() // "zxt"
    zxt.bornYear() // 1994
    
    //                
    
    var xiaoMing = {
      name: "xiaoming",
      age: 18 //     18 
    }
    
    zxt.speak.call(xiaoMing)
    // "xiaoming" this    xiaoMing    
    zxt.bornYear.call(xiaoMing)
    // 1994     1998,    this      zxt    
    
    
    したがって、ES 6の矢印関数は、4つの標準的なバインディング規則を使用するのではなく、現在の語法のスコープに基づいて、thisを決定します.具体的には、矢印関数は、外層関数によって呼び出されたthisバインディングを継承します.
    結び目
    以上がjavascriptの中のすべてのthisバインディングの場合であり、es 6の前に前述した4つのバインディング規則は、任意の関数呼出状況をカバーすることができます.es 6標準が実施された後、関数の拡張に対して矢印関数が追加されました.これまでとは違って、矢印関数の作動領域は矢印関数定義時にある作用領域にあります.
    前の4つのバインディング規則については、各規則の呼び出し条件を把握することによって、thisがどの役割領域に結合されているかをよく理解することができます.