你不懂JS:this與對象原型 第一章:this是什么暇昂?

官方中文版原文鏈接

感謝社區(qū)中各位的大力支持莺戒,譯者再次奉上一點點福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠急波,并抽取幸運大獎:點擊這里領(lǐng)取

JavaScript中最令人困惑的機制之一就是this關(guān)鍵字从铲。它是一個在每個函數(shù)作用域中自動定義的特殊標識符關(guān)鍵字,但即便是一些老練的開發(fā)者也對它到底指向什么感到困擾幔崖。

任何足夠 先進 的技術(shù)都跟魔法沒有區(qū)別食店。-- Arthur C. Clarke

JavaScript的this機制實際上沒有 那么 先進,但是開發(fā)者們總是在大腦中引用這句話來表達“復(fù)雜”和“混亂”赏寇,毫無疑問吉嫩,如果沒有清晰的理解,在 你的 困惑中this可能看起來就是徹頭徹尾的魔法嗅定。

注意: “this”這個詞是在一般的論述中極常用的代詞自娩。所以,特別是在口頭論述中渠退,很難確定我們是在將“this”作為一個代詞使用忙迁,還是在將它作為一個實際的關(guān)鍵字識別符使用。為了表意清晰碎乃,我會總是使用this來代表特殊的關(guān)鍵字姊扔,而在其他情況下使用“this”或 this 或this。

為什么用 this梅誓?

如果對于那些老練的JavaScript開發(fā)者來說this機制都是如此的令人費解恰梢,那么有人會問為什么這種機制會有用?它帶來的麻煩不是比好處多嗎梗掰?在講解 如何 有用之前嵌言,我們應(yīng)當先來看看 為什么 有用。

讓我們試著展示一下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()函數(shù)對多個 環(huán)境 對象(meyou)進行復(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機制提供了更優(yōu)雅的方式來隱含地“傳遞”一個對象引用缓窜,導(dǎo)致更加干凈的API設(shè)計和更容易的復(fù)用。

你的使用模式越復(fù)雜,你就會越清晰地看到:將執(zhí)行環(huán)境作為一個明確參數(shù)傳遞禾锤,通常比傳遞this執(zhí)行環(huán)境要亂私股。當我們探索對象和原型時,你將會看到一組可以自動引用恰當執(zhí)行環(huán)境對象的函數(shù)是多么有用恩掷。

困惑

我們很快就要開始講解this是如何 實際 工作的倡鲸,但我們首先要摒棄一些誤解——它實際上 不是 如何工作的。

在開發(fā)者們用太過于字面的方式考慮“this”這個名字時就會產(chǎn)生困惑黄娘。這通常會產(chǎn)生兩種臆測峭状,但都是不對的。

它自己

第一種常見的傾向是認為this指向函數(shù)自己逼争。至少优床,這是一種語法上的合理推測。

為什么你想要在函數(shù)內(nèi)部引用它自己誓焦?最通常的理由是遞歸(在函數(shù)內(nèi)部調(diào)用它自己)這樣的情形胆敞,或者是一個在第一次被調(diào)用時會解除自己綁定的事件處理器。

初次接觸JS機制的開發(fā)者們通常認為杂伟,將函數(shù)作為一個對象(JavaScript中所有的函數(shù)都是對象R撇恪),可以讓你在方法調(diào)用之間儲存 狀態(tài)(屬性中的值)赫粥。這當然是可能的观话,而且有一些有限的用處,但這本書的其余部分將會闡述許多其他的模式越平,提供比函數(shù)對象 更好 的地方來存儲狀態(tài)频蛔。

過一會兒我們將探索一個模式,來展示this是如何不讓一個函數(shù)像我們可能假設(shè)的那樣喧笔,得到它自身的引用的帽驯。

考慮下面的代碼龟再,我們試圖追蹤函數(shù)(foo)被調(diào)用了多少次:

function foo(num) {
    console.log( "foo: " + num );

    // 追蹤`foo`被調(diào)用了多少次
    this.count++;
}

foo.count = 0;

var i;

for (i=0; i<10; i++) {
    if (i > 5) {
        foo( i );
    }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

// `foo`被調(diào)用了多少次书闸?
console.log( foo.count ); // 0 -- 這他媽怎么回事……?

foo.count 依然0, 即便四個console.log語句明明告訴我們foo(..)實際上被調(diào)用了四次利凑。這種失敗來源于對于this (在this.count++中)的含義進行了 過于字面化 的解釋浆劲。

當代碼執(zhí)行foo.count = 0時,它確實在函數(shù)對象foo中加入了一個count屬性哀澈。但是對于函數(shù)內(nèi)部的this.count引用牌借,this其實 根本就不 指向那個函數(shù)對象,即便屬性名稱一樣割按,但根對象也不同膨报,因而產(chǎn)生了混淆。

注意: 一個負責任的開發(fā)者 應(yīng)當 在這里提出一個問題:“如果我遞增的count屬性不是我以為的那個,那是哪個count被我遞增了现柠?”院领。實際上,如果他再挖的深一些够吩,他會發(fā)現(xiàn)自己不小心創(chuàng)建了一個全局變量count(第二章解釋了這是 如何 發(fā)生的)比然,而且它當前的值是NaN。當然周循,一旦他發(fā)現(xiàn)這個不尋常的結(jié)果后强法,他會有一堆其他的問題:“它怎么是全局的?為什么它是NaN而不是某個正確的計數(shù)值湾笛?”饮怯。(見第二章)

與停在這里來深究為什么this引用看起來不是如我們 期待 的那樣工作,并且回答那些尖銳且重要的問題相反嚎研,許多開發(fā)者簡單地完全回避這個問題硕淑,轉(zhuǎn)向一些其他的另類解決方法,比如創(chuàng)建另一個對象來持有count屬性:

function foo(num) {
    console.log( "foo: " + num );

    // 追蹤foo被調(diào)用了多少次
    data.count++;
}

var data = {
    count: 0
};

var i;

for (i=0; i<10; i++) {
    if (i > 5) {
        foo( i );
    }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

// foo被調(diào)用了多少次嘉赎?
console.log( data.count ); // 4

雖然這種方式確實“解決”了問題置媳,但不幸的是它簡單地忽略了真正的問題——缺乏對于this的含義和其工作方式上的理解——反而退回到了一個他更加熟悉的機制的舒適區(qū):詞法作用域。

注意: 詞法作用域是一個完善且有用的機制公条;我不是在用任何方式貶低它的作用(參見本系列的 "作用域與閉包")拇囊。但在如何使用this這個問題上總是靠 ,而且通常都犯 靶橱,并不是一個退回到詞法作用域寥袭,而且從不學(xué)習(xí) 為什么 this不跟你合作的好理由。

為了從函數(shù)對象內(nèi)部引用它自己关霸,一般來說通過this是不夠的传黄。你用通常需要通過一個指向它的詞法標識符(變量)得到函數(shù)對象的引用。

考慮這兩個函數(shù):

function foo() {
    foo.count = 4; // `foo` 引用它自己
}

setTimeout( function(){
    // 匿名函數(shù)(沒有名字)不能引用它自己
}, 10 );

第一個函數(shù)队寇,稱為“命名函數(shù)”膘掰,foo是一個引用,可以用于在它內(nèi)部引用自己佳遣。

但是在第二個例子中识埋,傳遞給setTimeout(..)的回調(diào)函數(shù)沒有名稱標識符(所以被稱為“匿名函數(shù)”),所以沒有恰當?shù)霓k法引用函數(shù)對象自己零渐。

注意: 在函數(shù)中有一個老牌兒但是現(xiàn)在被廢棄的窒舟,而且令人皺眉頭的arguments.callee引用 指向當前正在執(zhí)行的函數(shù)的函數(shù)對象。這個引用通常是匿名函數(shù)在自己內(nèi)部訪問函數(shù)對象的唯一方法诵盼。然而惠豺,最佳的辦法是完全避免使用匿名函數(shù)银还,至少是對于那些需要自引用的函數(shù),而使用命名函數(shù)(表達式)洁墙。arguments.callee已經(jīng)被廢棄而且不應(yīng)該再使用见剩。

對于當前我們的例子來說,另一個 好用的 解決方案是在每一個地方都使用foo標識符作為函數(shù)對象的引用扫俺,而根本不用this

function foo(num) {
    console.log( "foo: " + num );

    // 追蹤`foo`被調(diào)用了多少次
    foo.count++;
}

foo.count = 0;

var i;

for (i=0; i<10; i++) {
    if (i > 5) {
        foo( i );
    }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

// `foo`被調(diào)用了多少次苍苞?
console.log( foo.count ); // 4

然而,這種方法也類似地回避了對this真正 理解狼纬,而且完全依靠變量foo的詞法作用域羹呵。

另一種解決問題的方法是強迫this指向foo函數(shù)對象:

function foo(num) {
    console.log( "foo: " + num );

    // 追蹤`foo`被調(diào)用了多少次
    // 注意:由于`foo`的被調(diào)用方式(見下方),`this`現(xiàn)在確實是`foo`
    this.count++;
}

foo.count = 0;

var i;

for (i=0; i<10; i++) {
    if (i > 5) {
        // 使用 `call(..)`疗琉,我們可以保證`this`指向函數(shù)對象(`foo`)
        foo.call( foo, i );
    }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

// `foo`被調(diào)用了多少次冈欢?
console.log( foo.count ); // 4

與回避this相反,我們接受它盈简。 我們將會更完整地講解這樣的技術(shù) 如何 工作凑耻,所以如果你依然有點兒糊涂,不要擔心柠贤!

它的作用域

第二常見的對this的含義的誤解香浩,是它不知怎的指向了函數(shù)的作用域。這是一個刁鉆的問題臼勉,因為在某一種意義上它有正確的部分邻吭,而在另外一種意義上,它是嚴重的誤導(dǎo)宴霸。

明確地說囱晴,this不會以任何方式指向函數(shù)的 詞法作用域。作用域好像是一個將所有可用標識符作為屬性的對象瓢谢,這從內(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.皂林,而僅對標識符進行詞法引用朗鸠。

然而,寫下這段代碼的開發(fā)者試圖用thisfoo()bar()的詞法作用域間建立一座橋础倍,使得bar()可以訪問foo()內(nèi)部作用域的變量a烛占。這樣的橋是不可能的。 你不能使用this引用在詞法作用域中查找東西沟启。這是不可能的忆家。

每當你感覺自己正在試圖使用this來進行詞法作用域的查詢時,提醒你自己:這里沒有橋德迹。

什么是this芽卿?

我們已經(jīng)列舉了各種不正確的臆想,現(xiàn)在讓我們把注意力this機制是如何真正工作的胳搞。

我們早先說過卸例,this不是編寫時綁定,而是運行時綁定肌毅。它依賴于函數(shù)調(diào)用的上下文條件筷转。this綁定和函數(shù)聲明的位置無關(guān),反而和函數(shù)被調(diào)用的方式有關(guān)悬而。

當一個函數(shù)被調(diào)用時旦装,會建立一個活動記錄,也稱為執(zhí)行環(huán)境摊滔。這個記錄包含函數(shù)是從何處(call-stack)被調(diào)用的阴绢,函數(shù)是 如何 被調(diào)用的,被傳遞了什么參數(shù)等信息艰躺。這個記錄的屬性之一呻袭,就是在函數(shù)執(zhí)行期間將被使用的this引用。

下一章中腺兴,我們將會學(xué)習(xí)尋找函數(shù)的 調(diào)用點(call-site) 來判定它的執(zhí)行如何綁定this左电。

復(fù)習(xí)

對于那些沒有花時間學(xué)習(xí)this綁定機制如何工作的JavaScript開發(fā)者來說,this綁定一直是困惑的根源页响。猜測篓足,試錯,或者盲目地從Stack Overflow的回答中復(fù)制粘貼闰蚕,都不是有效或正確利用this這么重要的機制的方法栈拖。

為了學(xué)習(xí)this,你必須首先學(xué)習(xí)this不是 什么没陡,不論是哪種把你誤導(dǎo)至何處的臆測或誤解涩哟。this既不是函數(shù)自身的引用索赏,也不是函數(shù)詞法作用域的引用。

this實際上是在函數(shù)被調(diào)用時建立的一個綁定贴彼,它指向 什么 是完全由函數(shù)被調(diào)用的調(diào)用點來決定的潜腻。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市器仗,隨后出現(xiàn)的幾起案子融涣,更是在濱河造成了極大的恐慌,老刑警劉巖精钮,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件暴心,死亡現(xiàn)場離奇詭異,居然都是意外死亡杂拨,警方通過查閱死者的電腦和手機专普,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弹沽,“玉大人檀夹,你說我怎么就攤上這事〔唛伲” “怎么了炸渡?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長丽已。 經(jīng)常有香客問我蚌堵,道長,這世上最難降的妖魔是什么沛婴? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任吼畏,我火速辦了婚禮,結(jié)果婚禮上嘁灯,老公的妹妹穿的比我還像新娘泻蚊。我一直安慰自己,他們只是感情好丑婿,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布性雄。 她就那樣靜靜地躺著,像睡著了一般羹奉。 火紅的嫁衣襯著肌膚如雪秒旋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天诀拭,我揣著相機與錄音迁筛,去河邊找鬼。 笑死炫加,一個胖子當著我的面吹牛瑰煎,可吹牛的內(nèi)容都是我干的铺然。 我是一名探鬼主播俗孝,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼酒甸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了赋铝?” 一聲冷哼從身側(cè)響起插勤,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎革骨,沒想到半個月后农尖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡良哲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年盛卡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片筑凫。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡滑沧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出巍实,到底是詐尸還是另有隱情滓技,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布棚潦,位于F島的核電站令漂,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏丸边。R本人自食惡果不足惜叠必,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望妹窖。 院中可真熱鬧挠唆,春花似錦、人聲如沸嘱吗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谒麦。三九已至俄讹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绕德,已是汗流浹背患膛。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留耻蛇,地道東北人踪蹬。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓胞此,卻偏偏與公主長得像,于是被迫代替她去往敵國和親跃捣。 傳聞我的和親對象是個殘疾皇子漱牵,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容