[譯] JavaScript 之 this 指南

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

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

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

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

這句話中,咱們不直接使用 Phelps,而是使用代詞「he」來指代他。相似地,JavaScript 中使用 this 關鍵字指向引用上下文中的對象。閉包

例:app

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 種不一樣的方法輸出 makemodelide

  • 使用 this 時,this.make+ " " +this.model 中的 this 指的是在上下文中的對象,也就是 car,則 this.makecar.makethis.modelcar.model
  • 使用點操做符時,咱們能夠直接訪問對象的屬性 car.makecar.model

this

如今咱們已經瞭解了什麼是 this 以及它 最基本的用法,爲方便記憶,咱們將列出一些場景,並分別舉例說明。函數

根據出現的位置,this 可分爲如下幾種狀況:post

  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
複製代碼

該例中,在全局對象中定義了 make, modelfullName,對象 car 中也實現了 fullName 方法。當使用 car 調用該方法時,this 指向該對象內的變量;而使用另外兩種調用方式時,this 指向全局變量。

3. 單獨使用 this

當單獨使用 this,不依附於任何函數或者對象時,指向全局對象。

這裏的 this 指向全局變量 name

4. 在事件內使用 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() 解決這個問題。

該例中利用 call() 方法使 anotherCar 對象調用 fullName(),該對象中本來並無 fullName() 方法,但輸出了 Ferrari Italia

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

例2:

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() 解決這個問題。

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 賦值,而後在函數內使用。

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 ),但在該例中,也可使用箭頭函數來解決:

能夠看出,在 forEach() 中使用箭頭函數就能夠解決該問題,而不是進行綁定或暫存 this。這是因爲箭頭函數綁定了上下文,this 實際上指向原始上下文或原始對象。

例5:

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 綁定到全局做用域上,以下圖:

爲便於觀察,咱們輸出了 this。能夠看到回調函數中的 this 指向了全局做用域。繼續建立全局變量 makemodel 以下例:

顯而易見,回調函數中的輸出了全局變量 makemodel,再次證實了 this 指向全局對象。

爲獲得指望結果,咱們將使用 bind()car 強制綁定到回調函數中。以下:

完成!

毫無疑問,this 是很是有用的,但不容易理解。但願經過這篇文章你能夠逐漸瞭解它的使用方法。

若是這篇文章對您有所幫助,點個贊👏,加個關注👣 吧~

相關文章
相關標籤/搜索