1. 程式人生 > 實用技巧 >一起來學習一下this關鍵字

一起來學習一下this關鍵字

一、定義

  this是在函式被呼叫時確定的,它的指向完全取決於函式呼叫的地方,而不是它被宣告的地方(箭頭函式除外)。當一個函式被呼叫時,會建立一個執行上下文,它包含函式在哪裡呼叫(呼叫棧)、呼叫方式、引數等資訊,this屬性會記錄這些資訊,會在函式執行的過程中被用到。

  this在函式的指向有以下幾種場景:

    1)作為建構函式被new呼叫;

    2)作為物件的方法使用;

    3)作為函式直接呼叫;

    4)被call、apply、bind呼叫;

    5)箭頭函式中的this;

二、this在各種場景中的指向

  1、new 繫結

  函式如果作為建構函式使用new呼叫時,this繫結的是新建立的建構函式的例項。

functionTest() {
    console.log(this)
}
var o = new Test()       // 輸出: Test 例項,this 就是 o

  實際上使用new呼叫建構函式時,會依次執行以下操作:

    A:建立一個新物件;

    B:建構函式的prototype被賦值給這個新物件的_proto_;

    C:將新物件賦值給當前的this

    D:執行建構函式;

    E:如果函式沒有返回其他物件,那麼new表示式中的函式呼叫會自動返回這個新物件,如果返回的不是物件將被忽略;

  2、顯式繫結

   我們可以通過callapplybind

修改函式繫結的this,使其成為我們指定的物件。實現方法:通過方法的第一個引數來顯式繫結this

   call()方法與 apply()方法的作用相同,它們的區別僅在於接收引數的方式不同,call 方法接受的是引數列表,而 apply 方法接受的是一個引數陣列。

func.call(thisArg, arg1, arg2, ...)        // call 用法
func.apply(thisArg, [arg1, arg2, ...])     // apply 用法

   bind()會建立一個函式的例項,其 this 值會被繫結到傳給 bind()函式的值,並返回一個新的函式,且在呼叫新函式時,將給定引數列表作為原函式的引數序列的前若干項。

func.bind(thisArg[, arg1[, arg2[, ...]]])    // bind 用法
var food = {
    name: '漢堡',
    price: '5塊錢',
    getPrice: function(place) {
        console.log(place + this.price)
    }
}

food.getPrice('KFC ')   // 瀏覽器中輸出: "KFC 5塊錢"

var getPrice1 = food.getPrice.bind({ name: '雞腿', price: '7塊錢' }, '肯打雞 ')
getPrice1()       // 瀏覽器中輸出: "肯打雞 7塊錢"

  注意:如果你把nullundefined作為this的繫結物件傳入callapplybind,這些值在呼叫時會被忽略,實際應用的是預設繫結規則。

var a = 'hello'

function foo() {
    console.log(this.a)
}

foo.call(null)         // 瀏覽器中輸出: "hello"
  3、隱式繫結

  函式是否在某個上下文物件中呼叫,如果是的話this繫結的是那個上下文物件。

var a = 'hello'

var obj = {
    a: 'world',
    foo: function() {
        console.log(this.a)
    }
}

obj.foo()       // 瀏覽器中輸出: "world"

  4、預設繫結

  非嚴格模式下this繫結到全域性物件(瀏覽器下是winodw,node 環境是global),嚴格模式下this繫結到undefined(因為嚴格模式不允許this指向全域性物件)。

var a = 'hello'

function foo() {
    var a = 'world'
    console.log(this.a)
    console.log(this)
}

foo()             // 相當於執行 window.foo()  輸出: "hello" Window 物件

  注意:以下情況

var a = 'hello'

var obj = {
    a: 'world',
    foo: function() {
        console.log(this.a)
    }
}

var bar = obj.foo

bar()              // 瀏覽器中輸出: "hello"

  此時bar函式,也就是obj上的foo方法為什麼又指向了全域性物件呢,是因為bar方法此時是作為函式獨立呼叫的,所以此時的場景屬於預設繫結,而不是隱式繫結。這種情況和把方法作為回撥函式的場景類似:

var a = 'hello'

var obj = {
    a: 'world',
    foo: function() {
        console.log(this.a)
    }
}

function func(fn) {
    fn()
}

func(obj.foo)              // 瀏覽器中輸出: "hello"

引數傳遞實際上也是一種隱式的賦值,只不過這裡obj.foo方法是被隱式賦值給了函式func的形參fn,而之前的情景是自己賦值,兩種情景實際上類似。這種場景我們遇到的比較多的是setTimeoutsetInterval,如果回撥函式不是箭頭函式,那麼其中的this指向的就是全域性物件。

   5、this繫結的優先順序

    new 繫結 > 顯式繫結 > 隱式繫結 > 預設繫結||this 的判斷順序

    A:new 繫結:函式是否在new中呼叫?如果是的話this繫結的是新建立的物件;

    B:顯式繫結:函式是否是通過bindcallapply呼叫?如果是的話,this繫結的是指定的物件;

    C:隱式繫結:函式是否在某個上下文物件中呼叫?如果是的話,this繫結的是那個上下文物件;

    D:如果都不是的話,使用預設繫結。如果在嚴格模式下,就繫結到undefined,否則繫結到全域性物件;

  6、箭頭函式中的this

    箭頭函式是根據其宣告的地方來決定this

    

var a = 'hello'

var obj = {
    a: 'world',
    foo: () => {
        console.log(this.a)
    }
}

obj.foo()             // 瀏覽器中輸出: "hello"
var a = 20
var obj = {
    a: 40,
    foo:() => {
        console.log(this.a)
    
        function func() {
            this.a = 60
            console.log(this.a)
        }
    
        func.prototype.a = 50
        return func
    }
}

var bar = obj.foo()        // 瀏覽器中輸出: 20
bar()                      // 瀏覽器中輸出: 60
new bar()              // 瀏覽器中輸出: 60

  稍微解釋一下:

    1、var a = 20這句在全域性變數 window 上建立了個屬性a並賦值為 20;

    2、首先執行的是obj.foo(),這是一個箭頭函式,箭頭函式不建立新的函式作用域直接沿用語句外部的作用域,因此obj.foo()執行時箭頭函式中this是全域性 window,首先打印出   window 上的屬性a的值 20,箭頭函式返回了一個原型上有個值為 50 的屬性a的函式物件funcbar

    3、繼續執行的是bar(),這裡執行的是剛剛箭頭函式返回的閉包func,其內部的this指向 window,因此this.a修改了window.a的值為 60 並打印出來;

    4、然後執行的是new bar(),根據之前的表述,new 操作符會在func函式中建立一個繼承了func原型的例項物件並用this指向它,隨後this.a = 60又在例項物件上建立了一個屬性a,在之後的列印中已經在例項上找到了屬性a,因此就不繼續往物件原型上查找了,所以打印出第三個 60;