當我開始學習JavaScript
時,花了一些時間來理解JavaScript
中的this
關鍵字而且可以快速識別this
關鍵字所指向的對象。我發現理解this
關鍵字最困難的事情是,您一般會忘記在您已閱讀或觀看過一些JavaScript
課程或資源中解釋的不一樣案例狀況。在ES6
中引入箭頭函數後,事情變得更加混亂,由於箭頭函數this
以不一樣的方式處理關鍵字。我想寫這篇文章來陳述我學到的東西,並嘗試以一種能夠幫助任何正在學習JavaScript
而且難以理解this
關鍵字的人的方式來解釋它。數組
您可能知道,執行任何JavaScript
行的環境(或scope
)稱爲「執行上下文」。Javascript
運行時維護這些執行上下文的堆棧,而且當前正在執行存在於該堆棧頂部的執行上下文。this
變量引用的對象每次更改執行上下文時都會更改。瀏覽器
默認狀況下,執行上下文是全局的,這意味着若是代碼做爲簡單函數調用的一部分執行,則該this
變量將引用全局對象。在瀏覽器的狀況下,全局對象是window
對象。例如,在Node.js
環境中,this值是一個特殊對象global
。bash
例如,嘗試如下簡單的函數調用:閉包
function foo () {
console.log("Simple function call");
console.log(this === window);
}
foo();
複製代碼
調用foo()
,獲得輸出:app
「Simple function call」
true
複製代碼
證實這裏的this
指向全局對象,此例中爲window
。函數
注意,若是實在嚴格模式下,this
的值將是undefined
,由於在嚴格模式下全局對象指向undefined
而不是window
。post
試一下以下示例:學習
function foo () {
'use strict';
console.log("Simple function call");
console.log(this === window);
}
foo();
複製代碼
輸出:ui
「Simple function call」
false
複製代碼
咱們再來試下有構造函數的:this
function Person(first_name, last_name) {
this.first_name = first_name;
this.last_name = last_name;
this.displayName = function() {
console.log(`Name: ${this.first_name} ${this.last_name}`);
};
}
複製代碼
建立Person
實例:
let john = new Person('John', 'Reid');
john.displayName();
複製代碼
獲得結果:
"Name: John Reid"
複製代碼
這裏發生了什麼?當咱們調用 new Person
,JavaScript
會在Person
函數內建立一個新對象並把它保存爲this
。接着,first_name
, last_name
和 displayName
屬性會被添加到新建立的this
對象上。以下:
你會注意到在Person
的執行上下文中建立了this
對象,這個對象有first_name
, last_name
和 displayName
屬性。但願您能根據上圖理解this
對象是如何建立並添加屬性的。
咱們已經探討了兩種相關this
綁定的普通案例我不得不提出下面這個更加困惑的例子,以下函數:
function simpleFunction () {
console.log("Simple function call")
console.log(this === window);
}
複製代碼
咱們已經知道若是像下面這樣做爲簡單函數調用,this
關鍵字將指向全局對象,此例中爲window
對象。
simpleFunction()
複製代碼
所以,獲得輸出:
「Simple function call」
true
複製代碼
建立一個簡單的user
對象:
let user = {
count: 10,
simpleFunction: simpleFunction,
anotherFunction: function() {
console.log(this === window);
}
}
複製代碼
如今,咱們有一個simpleFunction
屬性指向simpleFunction
函數,一樣添加另外一個屬性調用anotherFunction
函數方法。
若是調用user.simpleFunction()
,獲得輸出:
「Simple function call」
false
複製代碼
爲何會這樣呢?由於simpleFunction()
如今是user
對象的一個屬性,因此this
指向這個user
對象而不是全局對象。
當咱們調用user.anotherFunction
,也是同樣的結果。this
關鍵字指向user
對象。因此,console.log(this === window);
應該返回false
:
false
複製代碼
再來,如下操做會返回什麼呢?
let myFunction = user.anotherFunction;
myFunction();
複製代碼
如今,獲得結果:
true
複製代碼
因此這又發生了什麼?在這個例子中,咱們發起普通函數調用。正如以前所知,若是一個方法以普通函數方式執行,那麼this
關鍵字將指向全局對象(在這個例子中是window
對象)。因此console.log(this === window);
輸出true
。
再看一個例子:
var john = {
name: 'john',
yearOfBirth: 1990,
calculateAge: function() {
console.log(this);
console.log(2016 - this.yearOfBirth);
function innerFunction() {
console.log(this);
}
innerFunction();
}
}
複製代碼
調用john.calculateAge()
會發生什麼呢?
{name: "john", yearOfBirth: 1990, calculateAge: ƒ}
26
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
複製代碼
calculateAge
函數內部, this
指向 john
對象,可是,在innerFunction
函數內部,this
指向全局對象(本例中爲window
),有些人認爲這是JS
的bug,可是規則告訴咱們不管什麼時候一個普通函數被調用時,那麼this
將指向全局對象。
...
我所學的JavaScript
函數也是一種特殊的對象,每一個函數都有call
, apply
, bind
方法。這些方法被用來設置函數的執行上下文的this
值。
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.displayName = function() {
console.log(`Name: ${this.firstName} ${this.lastName}`);
}
}
複製代碼
建立兩個實例:
let person = new Person("John", "Reed");
let person2 = new Person("Paul", "Adams");
複製代碼
調用:
person.displayName();
person2.displayName();
複製代碼
結果:
Name: John Reed
Name: Paul Adams
複製代碼
call:
person.displayName.call(person2);
複製代碼
上面所作的事情就是設置this
的值爲person2
對象。所以,
Name: Paul Adams
複製代碼
apply:
person.displayName.apply([person2]);
複製代碼
獲得:
Name: Paul Adams
複製代碼
call
,apply
惟一的區別就是參數的傳遞形式,apply
應該傳遞一個數組,call
則應該單獨傳遞參數。
咱們用bind
來作一樣的事情,bind
返回一個新的方法,這個方法中的this
指向傳遞的第一個參數。
let person2Display = person.displayName.bind(person2);
複製代碼
調用person2Display
,獲得Name: Paul Adams
結果。
...
ES6中,有一個新方法定義函數。以下:
let displayName = (firstName, lastName) => {
console.log(Name: ${firstName} ${lastName});
};
複製代碼
不像一般的函數,箭頭函數沒有他們自身的this
關鍵字。他們只是簡單的使用寫在函數裏的this
關鍵字。他們有一個this
詞法變量。
ES5:
var box = {
color: 'green', // 1
position: 1, // 2
clickMe: function() { // 3
document.querySelector('body').addEventListener('click', function() {
var str = 'This is box number ' + this.position + ' and it is ' + this.color; // 4
alert(str);
});
}
}
複製代碼
若是調用:
box.clickMe()
複製代碼
彈出框內容將是This is box number undefined and it is undefined'
.
咱們一步一步來分析是怎麼回事。在//1
和//2
行,this
關鍵字能訪問到color
和position
屬性由於它指向box
對象。在clickMe
方法內部,this
關鍵字能訪問到color
和position
屬性由於它也指向box
對象。可是,clickMe
方法爲querySelector
方法定義了一個回調函數,而後這個回調函數以普通函數的形式調用,因此this
指向全局對象而非box
對象。固然,全局對象沒有定義color
和position
屬性,因此這就是爲何咱們獲得了undefined
值。
咱們能夠用ES5的方法來修復這個問題:
var box = {
color: 'green',
position: 1,
clickMe: function() {
var self = this;
document.querySelector('body').addEventListener('click', function() {
var str = 'This is box number ' + self.position + ' and it is ' + self.color;
alert(str);
});
}
}
複製代碼
添加 var self = this
,建立了一個可使用指向box
對象的this
關鍵字的閉包函數的工做區。咱們僅僅只須要在回調函數內使用self
變量。
調用:
box.clickMe();
複製代碼
彈出框內容This is box number 1 and it is green
。
怎麼使用箭頭函數可以達到上述效果呢?咱們將用箭頭函數替換點擊函數的回調函數。
var box = {
color: 'green',
position: 1,
clickMe: function() {
document.querySelector('body').addEventListener('click', () => {
var str = 'This is box number ' + this.position + ' and it is ' + this.color;
alert(str);
});
}
}
複製代碼
箭頭函數的神奇之處就是共享包裹它的this
詞法關鍵字。因此,本例中外層函數的this
共享給箭頭函數,這個外層函數的this
關鍵字指向box
對象,所以,color
和position
屬性將是有正確的green
和1
值。
再來一個:
var box = {
color: 'green',
position: 1,
clickMe: () => {
document.querySelector('body').addEventListener('click', () => {
var str = 'This is box number ' + this.position + ' and it is ' + this.color;
alert(str);
});
}
}
複製代碼
oh!如今又彈出了‘This is box number undefined and it is undefined’.
。爲何?
click
事件監聽函數閉包的this
關鍵字共享了包裹它的this
關鍵字。在本例中它被包裹的箭頭函數clickMe
,clickMe
箭頭函數的this
關鍵字指向全局對象,本例中是window
對象。因此this.color
和this.position
將會是undefined
由於window
對象沒有position
和color
屬性。
我想再給你看個在不少狀況下都會有幫助的map
函數,咱們定義一個Person
構造函數方法以下:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.displayName = function() {
console.log(`Name: ${this.firstName} ${this.lastName}`);
}
}
複製代碼
Person
的原型上添加myFriends
方法:
Person.prototype.myFriends = function(friends) {
var arr = friends.map(function(friend) {
return this.firstName + ' is friends with ' + friend;
});
console.log(arr);
}
複製代碼
建立一個實例:
let john = new Person("John", "Watson");
複製代碼
調用john.myFriends(["Emma", "Tom"])
,結果:
["undefined is friends with Emma", "undefined is friends with Tom"]
複製代碼
本例與以前的例子很是類似。myFriends
函數體內有this
關鍵字指向回調對象。可是,map
閉包函數內是一個普通函數調用。因此map
閉包函數內this
指向全局對象,本例中爲window
對象,所以this.firstName
undefined。如今,咱們試着修復這個狀況。
myFriends
函數體內指定this
爲其它變量如self
,以便map
函數內閉包使用它。Person.prototype.myFriends = function(friends) {
// 'this' keyword maps to the calling object
var self = this;
var arr = friends.map(function(friend) {
// 'this' keyword maps to the global object
// here, 'this.firstName' is undefined.
return self.firstName + ' is friends with ' + friend;
});
console.log(arr);
}
複製代碼
map
閉包函數使用bind
。Person.prototype.myFriends = function(friends) {
// 'this' keyword maps to the calling object
var arr = friends.map(function(friend) {
// 'this' keyword maps to the global object
// here, 'this.firstName' is undefined.
return this.firstName + ' is friends with ' + friend;
}.bind(this));
console.log(arr);
}
複製代碼
調用bind
會返回一個map
回調函數的副本,this
關鍵字映射到外層的this
關鍵字,也就是是調用myFriends
方法,this
指向這個對象。
map
回調函數爲箭頭函數。Person.prototype.myFriends = function(friends) {
var arr = friends.map(friend => `${this.firstName} is friends with ${friend}`);
console.log(arr);
}
複製代碼
如今,箭頭函數內的this
關鍵字將共享不曾包裹它的詞法做用域,也就是說實例myFriends
。
全部以上解決方案都將輸出結果:
["John is friends with Emma", "John is friends with Tom"]
複製代碼
...
在這一點上,我但願我已經設法使this
關鍵字概念對您來講有點平易近人。在本文中,我分享了我遇到的一些常見狀況以及如何處理它們,但固然,在構建更多項目時,您將面臨更多狀況。我但願個人解釋能夠幫助您在接近this
關鍵字綁定主題時保持堅實的基礎。若是您有任何問題,建議或改進,我老是樂於學習更多知識並與全部知名開發人員交流知識。請隨時寫評論,或給我留言!