用自然語言的角度理解JavaScript中的this關鍵字

發表於2015-09-08

在編寫JavaScript應用的時候,我們經常會使用this關鍵字。那麼this關鍵字究竟是怎樣工作的?它的設計有哪些好的地方,有哪些不好的地方?本文帶大家全面系統地認識這個老朋友。

小明正在跑步,他看起來很開心

這裡的小明是主語,如果沒有這個主語,那麼後面的代詞『他』將毫無意義。有了主語,代詞才有了可以指代的事物。

類比到JavaScript的世界中,我們在呼叫一個物件的方法的時候,需要先指明這個物件,再指明要呼叫的方法。

線上演示

在上面的例子中,第8行中的xiaoming指定了run方法執行時的主語。因此,在run中,我們才可以用this來代替xiaoming這個物件。可以看到this起了代詞的作用。

同樣的,對於一個JavaScript類,在將它初始化之後,我們也可以用類似的方法來理解:類的例項在呼叫其方法的時候,將作為主語,其方法中的this就自然變成了指代主語的代詞。

線上演示

這就是我認為this關鍵字設計得精彩的地方!如果將呼叫方法的語句(上面程式碼的第16行)和方法本身的程式碼連起來,像英語一樣讀,其實是完全通順的。

this的繫結

句子的主語是可以變的,例如在下面的場景中,run被賦值到小芳(xiaofang)身上之後,呼叫xiaofang.run,主語就變成了小芳!

線上演示

在這種情況下,句子還是通順的。所以,非常完美!

但是如果小明很摳門,不願意將run方法借給小芳以後,this就變成了小芳的話,那麼小明要怎麼做呢?他可以通過Function.prototype.bindrun執行時候的this永遠為小明自己。

線上演示

那麼同一個函式被多次bind之後,到底this是哪一次bind的物件呢?你可以自己嘗試看看。

callapply

Function.prototype.call允許你在呼叫一個函式的時候指定它的this的值。

線上演示

Function.prototype.applyFunction.prototype.call的功能是一模一樣的,區別進在於,apply裡將函式呼叫所需的所有引數放到一個陣列當中。

線上演示

那麼call/apply和上面的bind混用的時候是什麼樣的行為呢?這個也留給大家自行驗證。但是在一般情況下,我們應該避免混用它們,否則會造成程式碼檢查或者除錯的時候難以跟蹤this的值的問題。

當方法失去主語的時候,this不再有?

其實大家可以發現我的用詞,當一個function被呼叫的時候是有主語的時候,它是一個方法;當一個function被呼叫的時候是沒有主語的時候,它是一個函式。當一個函式執行的時候,它雖然沒有主語,但是它的this的值會是全域性物件。在瀏覽器裡,那就是window。當然了,前提是函式沒有被bind過,也不是被applycall所呼叫。

那麼function作為函式的情景有哪些呢?

首先,全域性函式的呼叫就是最簡單的一種。

立即呼叫的函式表示式(IIFE,Immediately-Invoked Function Expression)也是沒有主語的,所以它被呼叫的時候this也是全域性物件。

線上演示(包含上面兩個例子)

但是,當函式被執行在嚴格模式(strict-mode)下的時候,函式的呼叫時的this就是undefined了。這是很值得注意的一點。

不可見的呼叫

有時候,你沒有辦法看到你定義的函式是怎麼被呼叫的。因此,你就沒有辦法知道它的主語。下面是一個用jQuery新增事件監聽器的例子。

線上演示

在事件的回撥函式(第6行開始定義的匿名函式)裡面,this的值既不是window,又不是obj,而是頁面上idtext的HTML元素。

線上演示

這是因為匿名函式是被jQuery內部呼叫的,我們不知道它呼叫的時候的主語是什麼,或者是否被bind等函式修改過this的值。所以,當你將匿名函式交給程式的其他部分呼叫的時候,需要格外地謹慎。

如果我們想要在上面的回撥函式裡面使用obj的val值,除了直接寫obj.val之外,還可以在foo方法中用一個新的變數that來儲存foo執行時this的值。這樣說有些繞口,我們看下例子便知。

線上演示

另外一種方法就是為該匿名函式bind了。

線上演示

總結

在JavaScript中this的用法的確是千奇百怪,但是如果利用自然語言的方式來理解,一切就順理成章了。不知道你讀完這篇文章時候理解了嗎?還是睡著了?親……醒醒……

相關文章