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

