javascript
深入浅出 JavaScript 中的 this
為什么80%的碼農都做不了架構師?>>> ??
在 Java 等面向對象的語言中,this 關鍵字的含義是明確且具體的,即指代當前對象。一般在編譯期確定下來,或稱為編譯期綁定。而在 JavaScript 中,this 是動態綁定,或稱為運行期綁定的,這就導致 JavaScript 中的 this 關鍵字有能力具備多重含義,帶來靈活性的同時,也為初學者帶來不少困惑。本文僅就這一問題展開討論,閱罷本文,讀者若能正確回答 JavaScript 中的 What ’s this 問題,作為作者,我就會覺得花費這么多功夫,撰寫這樣一篇文章是值得的。
Java 語言中的 this
在 Java 中定義類經常會使用 this 關鍵字,多數情況下是為了避免命名沖突,比如在下面例子的中,定義一個 Point 類,很自然的,大家會使用 x,y 為其屬性或成員變量命名,在構造函數中,使用 x,y 為參數命名,相比其他的名字,比如 a,b,也更有意義。這時候就需要使用 this 來避免命名上的沖突。另一種情況是為了方便的調用其他構造函數,比如定義在 x 軸上的點,其 x 值默認為 0,使用時只要提供 y 值就可以了,我們可以為此定義一個只需傳入一個參數的構造函數。無論哪種情況,this 的含義是一樣的,均指當前對象。
?public?class?Point?{?private?int?x?=?0;?private?int?y?=?0;?public?Point(x,?y){?this.x?=?x;?this.y?=?y;?}?public?Point(y){?this(0,?y);?}?}JavaScript 語言中的 this
由于其運行期綁定的特性,JavaScript 中的 this 含義要豐富得多,它可以是全局對象、當前對象或者任意對象,這完全取決于函數的調用方式。JavaScript 中函數的調用有以下幾種方式:作為對象方法調用,作為函數調用,作為構造函數調用,和使用 apply 或 call 調用。下面我們將按照調用方式的不同,分別討論 this 的含義。
作為對象方法調用
在 JavaScript 中,函數也是對象,因此函數可以作為一個對象的屬性,此時該函數被稱為該對象的方法,在使用這種調用方式時,this 被自然綁定到該對象。
var?point?=?{?x?:?0,?y?:?0,?moveTo?:?function(x,?y)?{?this.x?=?this.x?+?1;?this.y?=?this.y?+?1;?}?};?point.moveTo(1,?1)//this?綁定到當前對象,即?point?對象作為函數調用(小心)
函數也可以直接被調用,此時 this 綁定到全局對象。在瀏覽器中,window 就是該全局對象。比如下面的例子:函數被調用時,this 被綁定到全局對象,接下來執行賦值語句,相當于隱式的聲明了一個全局變量,這顯然不是調用者希望的。
function?makeNoSense(x)?{?this.x?=?x;?}?makeNoSense(5);?alert(x);x;//?x?已經成為一個值為?5?的全局變量對于內部函數,即聲明在另外一個函數體內的函數,這種綁定到全局對象的方式會產生另外一個問題。我們仍然以前面提到的 point 對象為例,這次我們希望在 moveTo 方法內定義兩個函數,分別將 x,y 坐標進行平移。結果可能出乎大家意料,不僅 point 對象沒有移動,反而多出兩個全局變量 x,y。
?var?point?=?{?x?:?0,?y?:?0,?moveTo?:?function(x,?y)?{?//?內部函數var?moveX?=?function(x)?{?this.x?=?x;//this?綁定到了哪里?};?//?內部函數var?moveY?=?function(y)?{?this.y?=?y;//this?綁定到了哪里?};?moveX(x);?moveY(y);?}?};?point.moveTo(1,?1);?point.x;?//==>0?point.y;?//==>0?x;?//==>1?y;?//==>1這應該屬于 JavaScript 的設計缺陷,正確的設計方式是內部函數的 this 應該綁定到其外層函數對應的對象上,為了規避這一設計缺陷,聰明的 JavaScript 程序員想出了變量替代的方法,約定俗成,該變量一般被命名為 that。
var?point?=?{?x?:?0,?y?:?0,?moveTo?:?function(x,?y)?{?var?that?=?this;?//?內部函數var?moveX?=?function(x)?{?that.x?=?x;?};?//?內部函數var?moveY?=?function(y)?{?that.y?=?y;?}?moveX(x);?moveY(y);?}?};?point.moveTo(1,?1);?point.x;?//==>1?point.y;?//==>1
作為構造函數調用
JavaScript 支持面向對象式編程,與主流的面向對象式編程語言不同,JavaScript 并沒有類(class)的概念,而是使用基于原型(prototype)的繼承方式。相應的,JavaScript 中的構造函數也很特殊,如果不使用 new 調用,則和普通函數一樣。作為又一項約定俗成的準則,構造函數以大寫字母開頭,提醒調用者使用正確的方式調用。如果調用正確,this 綁定到新創建的對象上。
?function?Point(x,?y){?this.x?=?x;?this.y?=?y;?}使用 apply 或 call 調用
讓我們再一次重申,在 JavaScript 中函數也是對象,對象則有方法,apply 和 call 就是函數對象的方法。這兩個方法異常強大,他們允許切換函數執行的上下文環境(context),即 this 綁定的對象。很多 JavaScript 中的技巧以及類庫都用到了該方法。讓我們看一個具體的例子:
?function?Point(x,?y){?this.x?=?x;?this.y?=?y;?this.moveTo?=?function(x,?y){?this.x?=?x;?this.y?=?y;?}?}?var?p1?=?new?Point(0,?0);?var?p2?=?{x:?0,?y:?0};?p1.moveTo(1,?1);?p1.moveTo.apply(p2,?[10,?10]);在上面的例子中,我們使用構造函數生成了一個對象 p1,該對象同時具有 moveTo 方法;使用對象字面量創建了另一個對象 p2,我們看到使用 apply 可以將 p1 的方法應用到 p2 上,這時候 this 也被綁定到對象 p2 上。另一個方法 call 也具備同樣功能,不同的是最后的參數不是作為一個數組統一傳入,而是分開傳入的。
換個角度理解
如果像作者一樣,大家也覺得上述四種方式不方便記憶,過一段時間后,又搞不明白 this 究竟指什么。那么我向大家推薦 Yehuda Katz 的這篇文章:Understanding JavaScript Function Invocation and “this”。在這篇文章里,Yehuda Katz 將 apply 或 call 方式作為函數調用的基本方式,其他幾種方式都是在這一基礎上的演變,或稱之為語法糖。Yehuda Katz 強調了函數調用時 this 綁定的過程,不管函數以何種方式調用,均需完成這一綁定過程,不同的是,作為函數調用時,this 綁定到全局對象;作為方法調用時,this 綁定到該方法所屬的對象。
結束?
通過上面的描述,如果大家已經能明確區分各種情況下 this 的含義,這篇文章的目標就已經完成了。如果大家的好奇心再強一點,想知道為什么 this 在 JavaScript 中的含義如此豐富,那就得繼續閱讀下面的內容了。作者需要提前告知大家,下面的內容會比前面稍顯枯燥,如果只想明白 this 的含義,閱讀到此已經足夠了。如果大家不嫌枯燥,非要探尋其中究竟,那就一起邁入下一節吧。
原文:http://www.ibm.com/developerworks/cn/web/1207_wangqf_jsthis/index.html
轉載于:https://my.oschina.net/dlam/blog/468305
總結
以上是生活随笔為你收集整理的深入浅出 JavaScript 中的 this的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android Shell命令dumps
- 下一篇: gradle idea java ssm