【第764期】你不懂JS:this是什麼?

圖片
前言前端


臨近年底,相信接下來的文章正是你所須要的。本文由前端早讀課專欄做者@HetfieldJoe翻譯受權原創分享。閉包

ps:若是想看代碼,可經過點擊圖片查看ide


正文從這開始~函數


這是 你不懂JS:this與對象原型 第一章:this是什麼?學習


JavaScript中最使人困惑的機制之一就是this關鍵字。它是一個在每一個函數做用域中自動定義的特殊標識符關鍵字,但即使是一些老練的開發者也對它到底指向什麼感到困擾。this


任何足夠 先進 的技術都跟魔法沒有區別。-- Arthur C. Clarkespa


JavaScript的this機制實際上沒有 那麼 先進,可是開發者們老是在大腦中引用這句話來表達「複雜」和「混亂」,毫無疑問,若是沒有清晰的理解,在 你的 困惑中this可能看起來就是徹頭徹尾的魔法。翻譯


注意: 「this」這個詞是在通常的論述中極經常使用的代詞。因此,特別是在口頭論述中,很難肯定咱們是在將「this」做爲一個代詞使用,仍是在將它做爲一個實際的關鍵字識別符使用。爲了表意清晰,我會老是使用this來表明特殊的關鍵字,而在其餘狀況下使用「this」或 this 或this。設計


爲何用 this?3d

若是對於那些老練的JavaScript開發者來講this機制都是如此的使人費解,那麼有人會問爲何這種機制會有用?它帶來的麻煩不是比好處多嗎?在講解 如何 有用以前,咱們應當先來看看 爲何 有用。


讓咱們試着展現一下this的動機和用途:

圖片


若是這個代碼段 如何 工做讓你困惑,不要擔憂!咱們很快就會講解它。只是簡要地將這些問題放在旁邊,以便於咱們能夠更清晰的探究 爲何。


這個代碼片斷容許identify()和speak()函數對多個 環境 對象(me和you)進行復用,而不是針對每一個對象定義函數的分離版本。


與使用this相反地,你能夠明確地將環境對象傳遞給identify()和speak()。

image.png


然而,this機制提供了更優雅的方式來隱含地「傳遞」一個對象引用,致使更加乾淨的API設計和更容易的複用。


你的使用模式越複雜,你就會越清晰地看到:將執行環境做爲一個明確參數傳遞,一般比傳遞this執行環境要亂。當咱們探索對象和原型時,你將會看到一組能夠自動引用恰當執行環境對象的函數是多麼有用。


困惑

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


在開發者們用太過於字面的方式考慮「this」這個名字時就會產生困惑。這一般會產生兩種臆測,但都是不對的。


它本身

第一種常見的傾向是認爲this指向函數本身。至少,這是一種語法上的合理推測。


爲何你想要在函數內部引用它本身?最一般的理由是遞歸(在函數內部調用它本身)這樣的情形,或者是一個在第一次被調用時會解除本身綁定的事件處理器。


初次接觸JS機制的開發者們一般認爲,將函數做爲一個對象(JavaScript中全部的函數都是對象!),可讓你在方法調用之間儲存 狀態(屬性中的值)。這固然是可能的,並且有一些有限的用處,但這本書的其他部分將會闡述許多其餘的模式,提供比函數對象 更好 的地方來存儲狀態。


過一下子咱們將探索一個模式,來展現this是如何不讓一個函數像咱們可能假設的那樣,獲得它自身的引用的。


考慮下面的代碼,咱們試圖追蹤函數(foo)被調用了多少次:

image.png


foo.count 依然 是0, 即使四個console.log語句明明告訴咱們foo(..)實際上被調用了四次。這種失敗來源於對於this (在this.count++中)的含義進行了 過於字面化 的解釋。


當代碼執行foo.count = 0時,它確實在函數對象foo中加入了一個count屬性。可是對於函數內部的this.count引用,this其實 根本就不 指向那個函數對象,即使屬性名稱同樣,但根對象也不一樣,於是產生了混淆。


注意: 一個負責任的開發者 應當 在這裏提出一個問題:「若是我遞增的count屬性不是我覺得的那個,那是哪一個count被我遞增了?」。實際上,若是他再挖的深一些,他會發現本身不當心建立了一個全局變量count(第二章解釋了這是 如何 發生的),並且它當前的值是NaN。固然,一旦他發現這個不尋常的結果後,他會有一堆其餘的問題:「它怎麼是全局的?爲何它是NaN而不是某個正確的計數值?」。(見第二章)


與停在這裏來深究爲何this引用看起來不是如咱們 期待 的那樣工做,而且回答那些尖銳且重要的問題相反,許多開發者簡單地徹底迴避這個問題,轉向一些其餘的另類解決方法,好比建立另外一個對象來持有count屬性:

image.png


雖然這種方式確實「解決」了問題,但不幸的是它簡單地忽略了真正的問題——缺少對於this的含義和其工做方式上的理解——反而退回到了一個他更加熟悉的機制的溫馨區:詞法做用域。


注意: 詞法做用域是一個完善且有用的機制;我不是在用任何方式貶低它的做用(參見本系列的 "做用域與閉包")。但在如何使用this這個問題上老是靠 猜,並且一般都犯 錯,並非一個退回到詞法做用域,並且從不學習 爲何 this不跟你合做的好理由。


爲了從函數對象內部引用它本身,通常來講經過this是不夠的。你用一般須要經過一個指向它的詞法標識符(變量)獲得函數對象的引用。


考慮這兩個函數:

image.png


第一個函數,稱爲「命名函數」,foo是一個引用,能夠用於在它內部引用本身。


可是在第二個例子中,傳遞給setTimeout(..)的回調函數沒有名稱標識符(因此被稱爲「匿名函數」),因此沒有恰當的辦法引用函數對象本身。


注意: 在函數中有一個老牌兒可是如今被廢棄的,並且使人皺眉頭的arguments.callee引用 也 指向當前正在執行的函數的函數對象。這個引用一般是匿名函數在本身內部訪問函數對象的惟一方法。然而,最佳的辦法是徹底避免使用匿名函數,至少是對於那些須要自引用的函數,而使用命名函數(表達式)。arguments.callee已經被廢棄並且不該該再使用。


對於當前咱們的例子來講,另外一個 好用的 解決方案是在每個地方都使用foo標識符做爲函數對象的引用,而根本不用this:

image.png


然而,這種方法也相似地迴避了對this的 真正 理解,並且徹底依靠變量foo的詞法做用域。


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

圖片


與迴避this相反,咱們接受它。 咱們將會更完整地講解這樣的技術 如何 工做,因此若是你依然有點兒糊塗,不要擔憂!


它的做用域

第二常見的對this的含義的誤解,是它不知怎的指向了函數的做用域。這是一個刁鑽的問題,由於在某一種意義上它有正確的部分,而在另一種意義上,它是嚴重的誤導。


明確地說,this不會以任何方式指向函數的 詞法做用域。做用域好像是一個將全部可用標識符做爲屬性的對象,這從內部來講是對的。可是JavasScript代碼不能訪問做用域「對象」。它是 引擎 的內部實現。


考慮下面代碼,它(失敗的)企圖跨越這個邊界,用this來隱含地引用函數的詞法做用域:

圖片


這個代碼段裏不僅有一個錯誤。雖然它看起來是在故意瞎搞,但你看到的這段代碼,是從公共的幫助論壇社區中被交換的真實代碼中提取出來的。真是不可思議對this的臆想是多麼的誤導人。


首先,試圖經過this.bar()來引用bar()函數。它幾乎能夠說是 碰巧 可以工做,咱們過一下子再解釋它是 如何 工做的。調用bar()最天然的方式是省略開頭的 this.,而僅對標識符進行詞法引用。


然而,寫下這段代碼的開發者試圖用this在foo()和bar()的詞法做用域間創建一座橋,使得bar()能夠訪問foo()內部做用域的變量a。這樣的橋是不可能的。 你不能使用this引用在詞法做用域中查找東西。這是不可能的。


每當你感受本身正在試圖使用this來進行詞法做用域的查詢時,提醒你本身:這裏沒有橋。


什麼是this?

咱們已經列舉了各類不正確的臆想,如今讓咱們把注意力this機制是如何真正工做的。


咱們早先說過,this不是編寫時綁定,而是運行時綁定。它依賴於函數調用的上下文條件。this綁定和函數聲明的位置無關,反而和函數被調用的方式有關。


當一個函數被調用時,會創建一個活動記錄,也稱爲執行環境。這個記錄包含函數是從何處(call-stack)被調用的,函數是 如何 被調用的,被傳遞了什麼參數等信息。這個記錄的屬性之一,就是在函數執行期間將被使用的this引用。


複習

對於那些沒有花時間學習this綁定機制如何工做的JavaScript開發者來講,this綁定一直是困惑的根源。猜想,試錯,或者盲目地從Stack Overflow的回答中複製粘貼,都不是有效或正確利用this這麼重要的機制的方法。


爲了學習this,你必須首先學習this不是 什麼,不管是哪一種把你誤導至何處的臆測或誤解。this既不是函數自身的引用,也不是函數詞法做用域的引用。


this其實是在函數被調用時創建的一個綁定,它指向 什麼 是徹底由函數被調用的調用點來決定的。

相關文章
相關標籤/搜索