JavaScriptにおけるthisバインディングの詳細解

9804 ワード

転載は出典を明記してください.https://segmentfault.com/a/11... thisはJavascriptの中で最も味わい深い特性と言えます.高校英語の様々な時制のようです.例えば、受動的な時制、過去の進行時のように、何回見逃しても、次の間違いがあります.本論文は「あなたの知らないJavaScript上巻」に啓発し、javasriptのthisに対してまとめを行う.thisを学習する第1のステップは、関数自体を指すのではなく、関数を指すのでもない作用領域を理解することである.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()
この例では、this関数がグローバルスコープで呼び出されるので、(1)の文のpersonはグローバルオブジェクトに結び付けられている(ブラウザではthis、nodeではwindow)ので、第(1)の文は自然に出力されるのはグローバルオブジェクトのglobal属性であり、もちろんnameである."global"関数は、person関数内で呼び出されたが、それでも第2の文のsayNameは、全体的なオブジェクトを指す.this関数がname属性を設定しても.
これはデフォルトのバインディング規則であり、javascriptにおいて最も一般的な関数呼び出しモードであり、personのバインディング規則も4つのバインディング規則の中で最も簡単なものであり、グローバル作用領域にバインディングされている.
デフォルトのバインディングの厳密なモード
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をグローバルオブジェクトに結びつけると、実際にはthisがバインディングされているので、上記のコードはエラーとなることがわかる.
陰式バインディング
関数が呼び出されたとき、関数にはいわゆる「ドロップポイント」があると、コンテキストオブジェクトがある場合には、関数のundefinedがこのコンテキストオブジェクトにバインドされます.上の話がはっきりしないと思ったら、やはりコードを見に来ます.
function say() {
    console.log(this.name)
}
var obj1 = {
    name: "zxt",
    say: say
}

var obj2 = {
    name: "zxt1",
    say: say
}
obj1.say() // zxt
obj2.say() // zxt1
簡単ですよね.上記のコードでは、thisobj1は、いわゆるobj2関数の足掛かりであり、専門的にはコンテキストオブジェクトであり、関数にこのコンテキストオブジェクトを指定すると、関数内部のsayは、自然にこのコンテキストオブジェクトを指している.これも一般的な関数コールモードである.
陰でバインディングするとコンテキストが失われます.
function say() {
    console.log(this.name)
}
var name = "global"
var obj = {
    name: "inside",
    say: say
}
var alias = obj.say //          (1) 
alias() //        "global"  (2)
ここで出力されているのが見えますが、なぜ上記と違ってthisと名前を変えただけですか?まず、上の(1)の文のコードを見てみます.javascriptでは、関数は対象であり、対象間は引用伝達であり、値伝達ではありません.したがって、第(1)の文コードは”global“、つまりobj.sayであり、alias = obj.say = sayは橋として機能しているだけであり、alias = sayは最終的にobj.say関数のアドレスを参照しているが、このオブジェクトとは関係がない.これはいわゆる「失われたコンテキスト」です.最終的にはalias関数が実行されますが、単にsay関数が実行され、alias関数が出力されます.
明示的なバインディング
明示的なバインディングは、名前の通りにthisをコンテキストに結びつける3つの明示的なバインディングの方法を提供しています.say"global"applycall.bindapplyの使い方は基本的に似ています.callは、関数を呼び出すパラメータを配列形式でapply(obj,[arg1,arg2,arg3,...]の呼び出し関数のパラメータを順次与える.call(obj,arg1,arg2,arg3,...)関数が実行されたら、新しい関数が返されます.コードで説明します.
//     
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
したがって、bindapplyの役割は、関数にコンテキストを実行するために結合され、明示的に結合されているので、関数内のthisは、自然にcallまたはcallによって呼び出されたオブジェクトの上に結合されていることがわかる.
//    
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
上記の例では、applyapplyの用法の違いを説明しています.call関数は、指定された実行コンテキストを結びつけた新しい関数を返します.それとも、上記のコードを例にしますか?
//    
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バインディング
最後に述べるbindバインディング規則とは、thisオペレータを介してコンストラクタを呼び出すときに起こるnewバインディングのことです.まず明確にしたいのは、javascriptには他の言語のようなクラスの概念がありません.コンストラクタも普通の関数だけです.コンストラクターの関数名は大文字で始まるだけです.thisオペレータによって呼び出すことができます.
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
上記の例では、まずnew関数が定義されています.普通に呼び出すこともできるし、構造関数として呼び出すこともできます.普通に呼び出した場合は、通常の関数で実行され、文字列が出力されます.Personオペレータを介して新しいオブジェクトが構成されています.次に、2つの呼び出し方法を見てみます.newそれぞれがどこで最初に一般呼出されたかに結び付けられている場合、先に説明したように、デフォルトバインディング規則が適用され、thisがグローバルオブジェクトにバインディングされている場合、グローバルオブジェクトにはそれぞれthisnameの2つの属性が追加されます.ageオペレータによって呼び出されると、関数は1つのオブジェクトに戻り、出力結果からnewのオブジェクトを参照してください.この返却されたオブジェクトに紐付けされています.したがって、thisバインディングとは、newオペレータを介して関数を呼び出したときに、新しいオブジェクトが生成され、このオブジェクトにコンストラクション内のnewをバインドすることを意味します.実際には、thisを使用して関数を呼び出して、次の動作が自動的に実行されます.
  • 新たなオブジェクト
  • を作成します.
  • この新しいオブジェクトはプロトタイプ接続されます.
  • この新しいオブジェクトは、関数呼び出しのjavascript
  • に結び付けられます.
  • 関数が他のオブジェクトに戻っていない場合、new式の関数呼び出しは自動的にこの新しいオブジェクト
  • に戻ります.
    4つのバインディングの優先度thisの4つのnewバインディング規則は、基本的にすべての関数呼び出しの場合をカバーしています.しかし、これらの4つの規則のうちの2つ以上を同時に適用すると、どのような場合、または4つのバインディングの優先順位はどうなりますか?まず、デフォルトバインディングの優先度が分かりやすいです.最低です.他のjavascriptバインディング規則を適用できない場合にのみ、デフォルトバインディングが呼び出されます.暗黙的バインディングと明示的バインディングですか?それともコードバーですか?コードは嘘をつかないことがあります.
    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)
    したがって、上記のコードではthisが実行され、thisの関数内部のobj1.speak()speakを指すので、(1)コード出力はもちろんthisであるが、obj1関数内のobj1からspeakに明示的に結合されている場合、出力結果はthisになり、この結果から、明示的なバインディングの優先度は暗黙的なバインディングより高いことが分かります.実は、obj2のこのコードは間接的にobj2を獲得しただけです.関数の参照は、前に述べたように、暗黙的なバインディングが文脈を失ってしまいます.はい、明示的なバインディングの優先度が陰的バインディングより高いなら、次にnewバインディングと明示的なバインディングを比較します.
    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)において、obj1.speak.call(obj2)関数内部のobj1.speakは、元々speakを指していたが、(2)においてはbarオペレータによって呼び出されたので、thisの関数内部のobj1は戻りの例に再び向けられている.これは、newバインディングの優先度が、明示的バインディングよりも高いことを示している.これにより、4つのバインディング規則の優先順位付けが得られた.
    newバインディング>明示的バインディング>陰的バインディング>標準バインディング
    矢印関数のthisバインディング
    矢印関数はES 6の重要な特性です.矢印関数のnewは、外層(関数または大域)の作用領域によって決定されます.関数体内のbarオブジェクトは、前に紹介した呼び出し時に結合されたオブジェクトではなく、定義時に存在するオブジェクトを意味します.例を挙げます.
    var a = 1
    var foo = () => {
        console.log(this.a) //         ,  this        
    }
    
    var obj = {
        a: 2
    }
    foo() // 1 ,        
    foo.call(obj) // 1,    , obj     ,        
    上記の例から、矢印関数のthis強制バインディングは矢印関数定義時に存在する作用領域であり、thisthisのようなバインディングを表示することによって変更できないことが分かりました.
    //         
    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バインディングを継承し、外層関数のapplyがどこにバインディングされているかに関係なく.
    結び目
    以上がcallにおけるthisすべてのバインディングの場合であり、第6に先立って前述した4つのバインディング規則は、任意の関数呼び出しの場合をカバーすることができ、S 6標準が実施された後、関数の拡張に対して矢印関数が追加され、以前とは違って、矢印関数の役割領域は矢印関数が定義された時に存在する役割領域にある.
    前の4つのバインディング規則については、各規則の呼び出し条件を把握することによって、どの作用領域に結合されているのかをよく理解することができる.
    全文が終わる