[譯] JavaScript 之 this 指南

JintNiu發表於2019-03-14

[譯] JavaScript 之 this 指南

原文連結:A guide to this in JavaScript
原文作者:Ashay Mandwarya
譯者:JintNiu
推薦理由:this 一直是 JavaScript 中的重難點,藉助這篇文章,重新認識並理解 this,加深印象。

this 無疑是 JavaScript 中使用最廣泛但又容易被誤解的關鍵字,今天我將會對其進行詳細的解釋。

當我們在學校學習英語代詞時:

Phelps is swimming fast because he wants to win the race.

這句話中,我們不直接使用 Phelps,而是使用代詞“he”來指代他。類似地,JavaScript 中使用 this 關鍵字指向引用上下文中的物件。

例:

var car = {
    make: "Lamborghini",
    model: "Huracán",
    fullName: function () {
        console.log(this.make + " " + this.model); 
        console.log(car.make + " " + car.model); 
    }
}
car.fullName();

// Lamborghini Huracán
// Lamborghini Huracán
複製程式碼

在上面的程式碼中,我們定義了一個具有屬性 makemodelfullName 的物件 car,其中 fullName 是一個函式,函式體內使用 2 種不同的方法輸出 makemodel

  • 使用 this 時,this.make+ " " +this.model 中的 this 指的是在上下文中的物件,也就是 car,則 this.makecar.makethis.modelcar.model
  • 使用點操作符時,我們可以直接訪問物件的屬性 car.makecar.model

this

現在我們已經瞭解了什麼是 this 以及它 最基本的用法,為方便記憶,我們將列出一些場景,並分別舉例說明。

根據出現的位置,this 可分為以下幾種情況:

  1. 在方法內使用
  2. 在函式內使用
  3. 單獨存在
  4. 在事件中使用
  5. call()apply()

1. 在方法內使用 this

this 在方法內使用時,指向其所屬的物件。

在物件內定義的函式稱為方法。再來看看汽車的例子:

var car = {
    make: "Lamborghini",
    model: "Huracán",
    fullName: function () {
        console.log(this.make + " " + this.model);
        console.log(car.make + " " + car.model);
    }
}
car.fullName();
複製程式碼

該例中,fullName() 即為方法,方法中的 this 指向物件 car

2. 在函式內使用 this

函式中的 this 就有些複雜了。與物件一樣,函式也具有屬性。函式每次執行時都會獲取 this,它指向呼叫它的物件。

this 實際上只是“先行物件”的一種快捷引用,也就是物件呼叫。
 —  javascriptissexy.com

如果函式未被某物件呼叫,則函式內的 this 屬於全域性物件,該全域性物件被稱為 window。在這種情況下,this 將指向全域性作用域中的變數。且看以下例子:

var make = "Mclaren";
var model = "720s"
function fullName() {
    console.log(this.make + " " + this.model);
}
var car = {
    make: "Lamborghini",
    model: "Huracán",
    fullName: function () {
        console.log(this.make + " " + this.model);
    }
}
car.fullName(); // Lmborghini Huracán
window.fullName(); // Mclaren 720S
fullName(); // Mclaren 720S
複製程式碼

[譯] JavaScript 之 this 指南

該例中,在全域性物件中定義了 make, modelfullName,物件 car 中也實現了 fullName 方法。當使用 car 呼叫該方法時,this 指向該物件內的變數;而使用另外兩種呼叫方式時,this 指向全域性變數。

3. 單獨使用 this

當單獨使用 this,不依附於任何函式或者物件時,指向全域性物件。

[譯] JavaScript 之 this 指南

這裡的 this 指向全域性變數 name

4. 在事件內使用 this

[譯] JavaScript 之 this 指南

JS 中有很多種事件型別,但為了描述簡單,這裡我們以點選事件為例。

每當單擊按鈕並觸發一個事件時,可以呼叫另一個函式來去執行某個任務。如果在函式內使用 this,則指向觸發事件中的元素。DOM 中,所有元素都以物件的形式儲存,也就是說網頁元素實際上就是 DOM 中的一個物件,因此每觸發一個事件時,this 就會指向該元素。

例:

<button onclick="this.style.display='none'">
  Remove Me!
</button>
複製程式碼

5. call(), apply() & bind()

  • bind:允許我們在方法中設定 this 指向
  • call&apply:允許我們藉助其他函式並在函式呼叫中改變 this 的指向。

有關 call(), apply()bind() 的知識會在另一篇文章闡述。

譯者注:可參考文章: [譯] 如何在 JavaScript 中使用 apply(?),call(?),bind(➰)

難點

理解掌握了 this 會使工作變輕鬆很多,但實際情況往往不是那麼如意。請看以下例子。

例1:

var car = {
    make: "Lamborghini",
    model: "Huracán",
    name: null,
    fullName: function () {
        this.name = this.make + " " + this.model;
        console.log(this.name); 
    }
}
var anotherCar = {
    make: "Ferrari",
    model: "Italia",
    name: null
}
anotherCar.name = car.fullName();

// Lamborghini Huracán
複製程式碼

結果並不是我們所期望的。分析其原因:當我們使用 this 呼叫另一個物件的方法時,只是為 anotherCar 分配了該方法,但實際呼叫者是 car。因此返回的是 Lamborghini 而不是 Ferrari。

我們可以使用 call() 解決這個問題。

[譯] JavaScript 之 this 指南

該例中利用 call() 方法使 anotherCar 物件呼叫 fullName(),該物件中原本並沒有 fullName() 方法,但輸出了 Ferrari Italia

另外,當我們輸出 car.nameanotherCar.name 的值時,前者輸出 null,而後者輸出了 Ferrari Italia,也就是說 fullName() 函式確實被 anotherCar 呼叫了,而不是被 car 呼叫。

例2:

[譯] JavaScript 之 this 指南

var cars = [
    { make: "Mclaren", model: "720s" },
    { make: "Ferrari", model: "Italia" }
]
var car = {
    cars: [{ make: "Lamborghini", model: "Huracán" }],
    fullName: function () {
        console.log(this.cars[0].make + " " + this.cars[0].model);
    }
}
var vehicle = car.fullName;
vehicle() // Mclaren 720s
複製程式碼

該例中,我們定義了一個全域性變數 cars,並且在物件 car 中也定義了同名變數,接著將 fullName() 方法賦給變數 vehicle,然後呼叫它。該變數屬於全域性變數,由於上下文的關係,this 指向的是全域性變數 cars 而不是區域性變數。

我們可以使用 bind() 解決這個問題。

[譯] JavaScript 之 this 指南

bind 改變了this 的指向,使變數 vehicle 指向區域性變數 car。也就是說,this 的指向取決於 car 的上下文環境。

例3:

var car = {
   cars: [
        { make: "Lamborghini", model: "Huracán" },
        { make: "Mclaren", model: "720s" },
        { make: "Ferrari", model: "Italia" }
    ],
    brand:"lamborghini",
    fullName: function () {
        this.cars.forEach(function(car){
            console.log(car.model + " " + this.brand);
        })
    }
}
car.fullName();

// Huracán undefined
// 720s undefined
// Italia undefined
複製程式碼

在以上程式碼中,fullName() 使用 forEach 迭代陣列 cars,每次迭代都產生一個沒有上下文的匿名函式,這類定義在函式內部的函式,稱之為閉包(closure)。閉包在 JavaScript 中非常重要,而且被廣泛使用。

另一個重要的概念是作用域(scope)。定義在函式內部的變數不能訪問其作用域以外的變數和屬性;匿名函式中的 this 不能訪問外部作用域,以至於 this 只能指向全域性物件。該例中,全域性物件中沒有定義 this 所要訪問的屬性 brand,因此輸出 undefined

以上問題的解決方法是:我們可以在匿名函式外為 this 賦值,然後在函式內使用。

[譯] JavaScript 之 this 指南

this 賦給變數 self,並代替函式體內的 this,輸出期望結果。

例4:

var car = {
    make: "Lamborghini",
    model: "Huracán",
    fullName: function (cars) {
        cars.forEach(function (vehicle) {
            console.log(vehicle + " " + this.model);
        })
    }
}
car.fullName(['lambo', 'ferrari', 'porsche']);

// lambo undefined
// ferrari undefined
// porsche undefined
複製程式碼

當無法使用 this 進行訪問時,可以使用變數 self 來儲存它(如例 3 ),但在該例中,也可以使用箭頭函式來解決:

[譯] JavaScript 之 this 指南

可以看出,在 forEach() 中使用箭頭函式就可以解決該問題,而不是進行繫結或暫存 this。這是由於箭頭函式繫結了上下文,this 實際上指向原始上下文或原始物件。

例5:

[譯] JavaScript 之 this 指南

var car = {
    make: "Lamborghini",
    model: "Huracán",
    fullName: function () {
        console.log(this.make + " " + this.model);
    }
}
var truck = {
    make: "Tesla",
    model: "Truck",
    fullName: function (callback) {
        console.log(this.make + " " + this.model);
        callback();
    }
}
truck.fullName(car.fullName);

// Tesla Truck
// undefined undefined
複製程式碼

上述程式碼中定義了兩個相同的物件,但其中一個包含回撥函式,回撥函式將作為引數傳入另一個函式,然後通過外部函式呼叫來完成某種操作。

該程式碼中物件 truckfullName 方法包含一個回撥函式,並在方法中直接進行呼叫。當將 car.fullName 作為引數呼叫 truck.fullName() 時,輸出 Tesla Truckundefined undefined

結果出乎意料。實際上,car.fullName 只是作為引數傳入,而不是由 truck 物件呼叫。換句話說,回撥函式呼叫了物件 car 的方法,但卻把 this 繫結到全域性作用域上,如下圖:

[譯] JavaScript 之 this 指南

為便於觀察,我們輸出了 this。可以看到回撥函式中的 this 指向了全域性作用域。繼續建立全域性變數 makemodel 如下例:

[譯] JavaScript 之 this 指南

顯而易見,回撥函式中的輸出了全域性變數 makemodel,再次證明了 this 指向全域性物件。

為得到期望結果,我們將使用 bind()car 強制繫結到回撥函式中。如下:

[譯] JavaScript 之 this 指南

完成!

毫無疑問,this 是非常有用的,但不容易理解。希望通過這篇文章你可以逐漸瞭解它的使用方法。

如果這篇文章對您有所幫助,點個贊?,加個關注? 吧~

[譯] JavaScript 之 this 指南

相關文章