天天看點

深度解析JavaScript的this關鍵字深度解析JavaScript的this關鍵字你的 this函數式程式設計與面向對象程式設計面向對象方法這與 this 有什麼關系?那麼,this 是什麼顯式綁定 this箭頭函數

深度解析JavaScript的this關鍵字深度解析JavaScript的this關鍵字你的 this函數式程式設計與面向對象程式設計面向對象方法這與 this 有什麼關系?那麼,this 是什麼顯式綁定 this箭頭函數

圖檔

作者|Austin Tackaberry

譯者|無明

這篇文章通過簡單的術語和一個真實的例子解釋了 this 是什麼以及為什麼說它很有用。

我發現,很多教程在解釋 JavaScript 的 this 時,通常會假設你擁有 Java、C++ 或 Python 等面向對象程式設計語言的背景。這篇文章主要面向那些對 this 沒有先入之見的人。我将嘗試解釋什麼是 this 以及為什麼它很有用。

或許你遲遲不肯深入探究 this,因為它看起來很奇怪,讓你心生畏懼。你之是以使用它,有可能僅僅是因為 StackOverflow 說你需要在 React 用它來完成一些事情。

在我們深入了解它的真正含義以及為什麼要使用它之前,我們首先需要了解函數式程式設計和面向對象程式設計之間的差別。

你可能知道也可能不知道,JavaScript 具有函數和面向對象的構造,你可以選擇關注其中一個或兩者兼而有之。

在我的 JavaScript 之旅的早期,我一方面擁抱函數式程式設計,一方面像避免瘟疫一樣排斥面向對象程式設計。我對面向對象關鍵字 this 不甚了解。其中的一個原因是我不明白它存在的必要性。在我看來,完全可以不依賴 this 就可以完成所有的事情。

在某種程度上,我的看法是對的。

你可能隻關注其中一種範式而從來不去了解另外一種,作為一名 JavaScript 開發者,你的局限性就展現在這裡。為了說明函數式程式設計和面向對象程式設計之間的差别,我将使用一組 Facebook 好友資料作為示例。

假設你正在建構一個使用者登入 Facebook 的 Web 應用,在登入後顯示一些 Facebook 好友的資料。你需要通路 Facebook 端點來擷取好友的資料,可能包含一些資訊,例如 firstName、lastName、username、numFriends、friendData、birthday 和 lastTenPosts。

你從(臆造的)Facebook API 獲得上面的資料。現在,你需要轉換它們,讓它們符合項目需要的格式。假設你要為每個使用者的朋友顯示以下内容:

它們的名字,格式為$ {firstName} $ {lastName};

三篇随機的文章;

從他們生日起到現在的天數。

函數式方法

如果使用函數式方法,就是将整個數組或數組的每個元素傳給一個傳回所需操作資料的函數:

你從原始資料開始(來自 Facebook API),為了将它們轉換為對你有用的資料,你将資料傳給一個函數,這個函數将輸出你可以在應用程式中顯示給使用者的資料。

你也可以通過類似的方式擷取三個随機文章并計算朋友生日至今的天數。

函數式方法就是指接受原始資料,将資料傳給一個或多個函數,并輸出對你有用的資料。

對于那些剛接觸程式設計和學習 JavaScript 的人來說,面向對象方法可能會更難掌握。面向對象是指你将每個朋友轉換為對象,對象包含了用于生成你所需内容的一切。

你可以建立包含 fullName 屬性的對象,以及 getThreeRandomPosts 和 getDaysUntilBirthday 函數。

const objectFriends = data.map(initializeFriend)

objectFriends[0].getThreeRandomPosts()

// Gets three of Bob Ross's posts

面向對象方法是為你的資料建立對象,這些對象包含了狀态和用于生成對你和你的項目有用的資料的資訊。

你可能沒有想過會寫出類似 initializeFriend 這樣的東西,你可能會認為它很有用。你可能還會注意到,它其實并非真正的面向對象。

getThreeRandomPosts 或 getDaysUntilBirthday 方法之是以有用,主要是因為閉包。因為使用了閉包,是以在 initializeFriend 傳回之後,它們仍然可以通路 data。

假設你寫了另一個方法,叫 greeting。請注意,在 JavaScript 中,方法隻是對象的一個屬性,這個屬性的值是一個函數。我們希望 greeting 可以做這些事情:

這樣可以嗎?

不行!

新建立對象的所有東西都可以通路 initializeFriend 的變量,但對象本身的屬性或方法不行。當然,你可能會問:

難道你不能用 data.firstName 和 data.lastName 來傳回 greeting 嗎?

當然可以。但如果我們還想在 greeting 中包含朋友生日至今的天數,該怎麼辦?我們必須以某種方式從 greeting 中調用 getDaysUntilBirthday 方法。

是時候讓 this 上場了!

在不同的情況下,this 代表的東西也不一樣。預設情況下,this 指向全局對象(在浏覽器中,就是 window 對象)。但光知道這點對我們并沒有太大幫助,對我來說有用的是 this 的這條規則:

如果 this 被用在一個對象的方法中,并且這個方法在對象的上下文中調用,那麼 this 就指向這個對象本身。

你會問:“在對象的上下文中調用……這又是什麼意思”?

别擔心,稍後我們會解釋這個。

是以,如果我們想在 greeting 中調用 getDaysUntilBirthday,可以直接調用 this.getDaysUntilBirthday,因為在這種情況下,this 指向對象本身。

注意:不要在全局作用域或在另一個函數作用域内的正常 ole 函數中使用 this!this 是一個面向對象的構造。是以,它隻在對象(或類)的上下文中有意義!

讓我們重構 initializeFriend,讓它使用 this:

現在,在執行完 intializeFriend 後,這個對象的所有東西都限定在對象本身。我們的方法不再依賴于閉包,它們将使用對象本身包含的資訊。

這是 this 的一種使用方式,現在回到之前的問題:為什麼說 this 因上下文不同而不同?

有時候,你希望 this 可以指向不一樣的東西,比如事件處理程式就是一個很好的例子。假設我們想在使用者點選連結時打開朋友的 Facebook 頁面。我們可能會在對象中添加一個 onClick 方法:

請注意,我們向對象添加了 username,讓 onFriendClick 可以通路它,這樣我們就可以在新視窗中打開朋友的 Facebook 頁面。現在編寫 HTML:

然後是 JavaScript:

在上面的代碼中,我們為 Bob Ross 建立了一個對象。我們獲得與 Bob Ross 相關的 DOM 元素。現在我們想要調用 onFriendClick 方法來打開 Bob 的 Facebook 頁面。應該沒問題吧?

什麼地方出了問題?

請注意,我們為 onclick 處理程式選擇的函數是 bobRossObj.onFriendClick。看到問題所在了嗎?如果我們像這樣重寫它:

現在你看到問題所在了嗎?當我們将 onclick 處理程式指定為 bobRossObj.onFriendClick 時,我們實際上是将 bobRossObj.onFriendClick 的函數作為參數傳給了處理程式。它不再“屬于”bobRossObj,也就是說 this 不再指向 bobRossObj。這個時候 this 實際上指向的是全局對象,是以 this.username 是 undefined 的。

是時候讓 bind 上場了!

我們需要做的是将 this 顯式綁定到 bobRossObj。我們可以使用 bind 來實作:

之前,this 是基于預設規則設定的。通過使用 bind,我們在 bobRossObj.onFriendClick 中将 this 的值顯式設定為對象本身,也就是 bobRossObj。

到目前為止,我們已經知道為什麼 this 很有用以及為什麼有時候需要顯式綁定 this。接下來我們要讨論的最後一個主題是箭頭函數。

你可能已經注意到,箭頭函數像是一個時髦的新事物。人們似乎很喜歡它們,因為它們簡潔而優雅。你可能已經知道它們與一般函數略有不同,但不一定非常清楚這些差別究竟是什麼。

或許箭頭函數的不同之處在于:

在箭頭函數内部,無論 this 處于什麼位置,它指的都是相同的東西。

讓我們用 initializeFriend 示例解釋一下。假設我們想在 greeting 中添加一個輔助函數:

這樣可以嗎?如果不行,要怎樣修改才行?

這樣當然是不行的。因為 getLastPost 不是在對象的上下文中調用的,是以 getLastPost 中的 this 會回退到預設規則,即指向全局對象。

“在對象的上下文中調用”可能是一個比較含糊的概念。要确定一個函數是否是在“對象的上下文中”被調用,最好的辦法是看一下函數是如何被調用的,以及是否有對象“附加”在函數上。

讓我們來看看執行 bobRossObj.onFriendClick() 時會發生什麼:“找到 bobRossObj 對象的 onFriendClick 屬性,調用配置設定給這個屬性的函數”。

再讓我們來看看執行 getLastPost() 時會發生什麼:”調用一個叫作 getLastPost 的函數”。有沒有注意到,這裡并沒有提及任何對象?

現在來測試一下。假設有一個叫作 functionCaller 的函數,它所做的事情就是調用其他函數:

}

如果我們這樣做會怎樣:functionCaller(bobRossObj.onFriendClick)?可不可以說 onFriendClick 是“在對象的上下文中”被調用的?this.username 的定義存在嗎?

讓我們來看一下:“找到 bobRossObj 對象的 onFriendClick 屬性。找到這個屬性的值(恰好是一個函數),将它傳給 functionCaller,并命名為 fn。現在,執行名為 fn 的函數”。請注意,函數在被調用之前已經從 bobRossObj 對象中“分離”,是以不是“在對象 bobRossObj 的上下文中”調用,是以 this.username 是 undefined 的。

讓箭頭函數來救場:

箭頭函數是在 greeting 中聲明的。我們知道,當我們在 greeting 中使用 this 時,它指向對象本身。是以,箭頭函數中的 this 指向的對象就是我們想要的。

英文原文:

https://medium.freecodecamp.org/a-deep-dive-into-this-in-javascript-why-its-critical-to-writing-good-code-7dca7eb489e7

【限時優惠】

極客時間《人工智能基礎課》專欄裡,工學博士、副教授王天一會帶你鞏固人工智能基礎,梳理人工智能知識架構,了解人工智能的最佳應用場景。

現在訂閱《人工智能基礎課》專欄,限時拼團價¥49,11 月 5 日立即恢複原價¥68。

訂閱後,每邀請一位好友購買,你可獲得 ¥12 現金返現,好友也将獲得 ¥6 返現。多邀多得,上不封頂,立即提現。

掃碼或點選「閱讀原文」,立享優惠。