JavaScript中this機制是如何真正工作的
this
不是編寫時綁定,而是運行時綁定。它依賴于函數調用的上下文條件。this
綁定與函數聲明的位置沒有任何關系,而與函數被調用的方式緊密相連。this
既不是函數自身的引用,也不是函數 詞法 作用域的引用。this
實際上是在函數被調用時建立的一個綁定,它指向 什么 是完全由函數被調用的調用點來決定的。
當一個函數被調用時,會建立一個稱為執(zhí)行環(huán)境的活動記錄。這個記錄包含函數是從何處(調用棧 —— call-stack)被調用的,函數是 如何 被調用的,被傳遞了什么參數等信息。這個記錄的屬性之一,就是在函數執(zhí)行期間將被使用的 this
引用。
為什么要用 this
?
如果對于那些老練的 JavaScript 開發(fā)者來說 this
機制都是如此的令人費解,那么有人會問為什么這種機制會有用?它帶來的麻煩不是比好處多嗎?在講解 如何 有用之前,我們應當先來看看 為什么 有用。
讓我們試著展示一下 this
的動機和用途:
function identify() { return this.name.toUpperCase(); } function speak() { var greeting = "Hello, I'm " + identify.call( this ); console.log( greeting ); } var me = { name: "Kyle" }; var you = { name: "Reader" }; identify.call( me ); // KYLE identify.call( you ); // READER speak.call( me ); // Hello, I'm KYLE speak.call( you ); // Hello, I'm READER
如果這個代碼段 如何 工作讓你困惑,不要擔心!我們很快就會講解它。只是簡要地將這些問題放在旁邊,以便于我們可以更清晰的探究 為什么。
這個代碼片段允許 identify()
和 speak()
函數對多個 環(huán)境 對象(me
和 you
)進行復用,而不是針對每個對象定義函數的分離版本。
與使用 this
相反地,你可以明確地將環(huán)境對象傳遞給 identify()
和 speak()
。
function identify(context) { return context.name.toUpperCase(); } function speak(context) { var greeting = "Hello, I'm " + identify( context ); console.log( greeting ); } identify( you ); // READER speak( me ); // Hello, I'm KYLE
然而,this
機制提供了更優(yōu)雅的方式來隱含地“傳遞”一個對象引用,導致更加干凈的API設計和更容易的復用。
你的使用模式越復雜,你就會越清晰地看到:將執(zhí)行環(huán)境作為一個明確參數傳遞,通常比傳遞 this
執(zhí)行環(huán)境要亂。當我們探索對象和原型時,你將會看到一組可以自動引用恰當執(zhí)行環(huán)境對象的函數是多么有用。
this的困惑
我們很快就要開始講解 this
是如何 實際 工作的,但我們首先要摒棄一些誤解——它實際上 不是 如何工作的。
在開發(fā)者們用太過于字面的方式考慮“this”這個名字時就會產生困惑。這通常會產生兩種臆測,但都是不對的。
第一種常見的傾向是認為 this
指向函數自己。至少,這是一種語法上的合理推測。
為什么你想要在函數內部引用它自己?最常見的理由是遞歸(在函數內部調用它自己)這樣的情形,或者是一個在第一次被調用時會解除自己綁定的事件處理器。
初次接觸 JS 機制的開發(fā)者們通常認為,將函數作為一個對象(JavaScript 中所有的函數都是對象?。?,可以讓你在方法調用之間儲存 狀態(tài)(屬性中的值)。這當然是可能的,而且有一些有限的用處,但這本書的其余部分將會闡述許多其他的模式,提供比函數對象 更好 的地方來存儲狀態(tài)。
this的作用域
對 this
的含義第二常見的誤解,是它不知怎的指向了函數的作用域。這是一個刁鉆的問題,因為在某一種意義上它有正確的部分,而在另外一種意義上,它是嚴重的誤導。
明確地說,this
不會以任何方式指向函數的 詞法作用域。作用域好像是一個將所有可用標識符作為屬性的對象,這從內部來說是對的。但是 JavasScript 代碼不能訪問作用域“對象”。它是 引擎 的內部實現(xiàn)。
考慮下面代碼,它(失敗的)企圖跨越這個邊界,用 this
來隱含地引用函數的詞法作用域:
function foo() { var a = 2; this.bar(); } function bar() { console.log( this.a ); } foo(); //undefined
這個代碼段里不只有一個錯誤。雖然它看起來是在故意瞎搞,但你看到的這段代碼,提取自在公共社區(qū)的幫助論壇中被交換的真實代碼。真是難以想象對 this
的臆想是多么的誤導人。
首先,試圖通過 this.bar()
來引用 bar()
函數。它幾乎可以說是 碰巧 能夠工作,我們過一會兒再解釋它是 如何 工作的。調用 bar()
最自然的方式是省略開頭的 this.
,而僅使用標識符進行詞法引用。
然而,寫下這段代碼的開發(fā)者試圖用 this
在 foo()
和 bar()
的詞法作用域間建立一座橋,使得bar()
可以訪問 foo()
內部作用域的變量 a
。這樣的橋是不可能的。 你不能使用 this
引用在詞法作用域中查找東西。這是不可能的。
每當你感覺自己正在試圖使用 this
來進行詞法作用域的查詢時,提醒你自己:這里沒有橋。
this調用點(Call-site)
為了理解 this
綁定,我們不得不理解調用點:函數在代碼中被調用的位置(不是被聲明的位置)。我們必須考察調用點來回答這個問題:這個 this
指向什么?
一般來說尋找調用點就是:“找到一個函數是在哪里被調用的”,但它不總是那么簡單,比如某些特定的編碼模式會使 真正的 調用點變得不那么明確。
考慮 調用棧(call-stack) (使我們到達當前執(zhí)行位置而被調用的所有方法的堆棧)是十分重要的。我們關心的調用點就位于當前執(zhí)行中的函數 之前 的調用。
我們來展示一下調用棧和調用點:
function baz() { // 調用棧是: `baz` // 我們的調用點是 global scope(全局作用域) console.log( "baz" ); bar(); // <-- `bar` 的調用點 } function bar() { // 調用棧是: `baz` -> `bar` // 我們的調用點位于 `baz` console.log( "bar" ); foo(); // <-- `foo` 的 call-site } function foo() { // 調用棧是: `baz` -> `bar` -> `foo` // 我們的調用點位于 `bar` console.log( "foo" ); } baz(); // <-- `baz` 的調用點
在分析代碼來尋找(從調用棧中)真正的調用點時要小心,因為它是影響 this
綁定的唯一因素。
注意: 你可以通過按順序觀察函數的調用鏈在你的大腦中建立調用棧的視圖,就像我們在上面代碼段中的注釋那樣。但是這很痛苦而且易錯。另一種觀察調用棧的方式是使用你的瀏覽器的調試工具。大多數現(xiàn)代的桌面瀏覽器都內建開發(fā)者工具,其中就包含 JS 調試器。在上面的代碼段中,你可以在調試工具中為 foo()
函數的第一行設置一個斷點,或者簡單的在這第一行上插入一個 debugger
語句。當你運行這個網頁時,調試工具將會停止在這個位置,并且向你展示一個到達這一行之前所有被調用過的函數的列表,這就是你的調用棧。所以,如果你想調查this
綁定,可以使用開發(fā)者工具取得調用棧,之后從上向下找到第二個記錄,那就是你真正的調用點。
到此這篇關于JavaScript中this機制是如何真正工作的的文章就介紹到這了,更多相關JavaScript中this關鍵字內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
IE與Firefox在JavaScript上的7個不同寫法小結
盡管那需要用長串的、沉悶的不同分支代碼來應付不同瀏覽器的日子已經過去,偶爾還是有必要做一些簡單的區(qū)分和目標檢測來確保某塊代碼能在用戶的機器上正常運行。2009-09-09layui實現(xiàn)左側菜單點擊右側內容區(qū)顯示
這篇文章主要為大家詳細介紹了layui實現(xiàn)左側菜單點擊右側內容區(qū)顯示,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-07-07如何用js 實現(xiàn)依賴注入的思想,后端框架思想搬到前端來
這篇文章主要介紹了js 實現(xiàn)依賴注入的思想,后端框架思想搬到前端來,需要的朋友可以參考下2015-08-08