原文連接: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
複製代碼
在上面的代碼中,咱們定義了一個具備屬性 make
,model
和 fullName
的對象 car
,其中 fullName
是一個函數,函數體內使用 2 種不一樣的方法輸出 make
和 model
。ide
this
時,this.make+ " " +this.model
中的 this
指的是在上下文中的對象,也就是 car
,則 this.make
爲 car.make
,this.model
爲 car.model
;car.make
和 car.model
。如今咱們已經瞭解了什麼是 this
以及它 最基本的用法,爲方便記憶,咱們將列出一些場景,並分別舉例說明。函數
根據出現的位置,this
可分爲如下幾種狀況:post
call()
和 apply()
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
。
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
, model
和 fullName
,對象 car
中也實現了 fullName
方法。當使用 car
調用該方法時,this
指向該對象內的變量;而使用另外兩種調用方式時,this
指向全局變量。
this
當單獨使用 this
,不依附於任何函數或者對象時,指向全局對象。
這裏的 this
指向全局變量 name
。
this
JS
中有不少種事件類型,但爲了描述簡單,這裏咱們以點擊事件爲例。
每當單擊按鈕並觸發一個事件時,能夠調用另外一個函數來去執行某個任務。若是在函數內使用 this
,則指向觸發事件中的元素。DOM 中,全部元素都以對象的形式儲存,也就是說網頁元素實際上就是 DOM 中的一個對象,所以每觸發一個事件時,this
就會指向該元素。
例:
<button onclick="this.style.display='none'">
Remove Me!
</button>
複製代碼
bind
:容許咱們在方法中設置 this
指向call
&apply
:容許咱們藉助其餘函數並在函數調用中改變 this
的指向。有關 call()
, apply()
和 bind()
的知識會在另外一篇文章闡述。
理解掌握了 this
會使工做變輕鬆不少,但實際狀況每每不是那麼如意。請看如下例子。
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.name
和 anotherCar.name
的值時,前者輸出 null
,然後者輸出了 Ferrari Italia
,也就是說 fullName()
函數確實被 anotherCar
調用了,而不是被 car
調用。
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
的上下文環境。
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
,輸出指望結果。
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
實際上指向原始上下文或原始對象。
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
複製代碼
上述代碼中定義了兩個相同的對象,但其中一個包含回調函數,回調函數將做爲參數傳入另外一個函數,而後經過外部函數調用來完成某種操做。
該代碼中對象 truck
的 fullName
方法包含一個回調函數,並在方法中直接進行調用。當將 car.fullName
做爲參數調用 truck.fullName()
時,輸出 Tesla Truck
和 undefined undefined
。
結果出乎意料。實際上,car.fullName
只是做爲參數傳入,而不是由 truck
對象調用。換句話說,回調函數調用了對象 car
的方法,但卻把 this
綁定到全局做用域上,以下圖:
爲便於觀察,咱們輸出了 this
。能夠看到回調函數中的 this
指向了全局做用域。繼續建立全局變量 make
和 model
以下例:
顯而易見,回調函數中的輸出了全局變量 make
和 model
,再次證實了 this
指向全局對象。
爲獲得指望結果,咱們將使用 bind()
將 car
強制綁定到回調函數中。以下:
毫無疑問,this
是很是有用的,但不容易理解。但願經過這篇文章你能夠逐漸瞭解它的使用方法。
若是這篇文章對您有所幫助,點個贊👏,加個關注👣 吧~