本文僅就這一問題展開討論,閱罷本文,讀者若能正確回答 JavaScript 中的 What 's this 問題,作為作者,我就會覺得花費這麼多功夫,撰寫這樣一篇文章是值得的。
我們要記住一句話:this永遠指向函數運行時所在的對象!而不是函數被創建時所在的對象。也即:誰調用,指向誰。切記…
本文將分三種情況來分析this對象到底身處何方。
1、普通函數中的this
無論this身處何處,第一要務就是要找到函數運行時的位置。
var name="全局"; function getName(){ var name="局部"; return this.name; }; alert(getName());
當this出現在全局環境的函數getName中時,此時函數getName運行時的位置在
alert(getName());
顯然,函數getName所在的對象是全局對象,即window,因此this的安身之處定然在window。此時的this指向window對象,則getName返回的this.name其實是window.name,因此alert出來的是“全局”!
那麼,當this不是出現在全局環境的函數中,而是出現在局部環境的函數中時,又會身陷何方呢?
var name="全局"; var xpg={ name:"局部", getName:function(){ return this.name; } }; alert(xpg.getName());
其中this身處的函數getName不是在全局環境中,而是處在xpg環境中。無論this身處何處,一定要找到函數運行時的位置。此時函數getName運行時的位置
alert(xpg.getName());
顯然,函數getName所在的對象是xpg,因此this的安身之處定然在xpg,即指向xpg對象,則getName返回的this.name其實是xpg.name,因此alert出來的是“局部”!
舉個例子鞏固一下:
var someone = { name: "Bob", showName: function(){ alert(this.name); } }; var other = { name: "Tom", showName: someone.showName } other.showName(); //Tom
this關鍵字雖然是在someone.showName中聲明的,但運行的時候是other.showName,所以this指向other.showName函數的當前對象,即other,故最後alert出來的是other.name。
2、閉包中的this
閉包也是個不安分子,本文暫且不對其過於贅述,簡而言之:所謂閉包就是在一個函數內部創建另一個函數,且內部函數訪問了外部的變量。
浪子this與痞子閉包混在一起,可見將永無寧日啊!
var name="全局"; var xpg={ name:"局部", getName:function(){ return function(){ return this.name; }; } }; alert(xpg.getName()());
此時的this明顯身處困境,竟然處在getName函數中的匿名函數裡面,而該匿名函數又調用了變量name,因此構成了閉包,即this身處閉包中。
無論this身處何處,一定要找到函數運行時的位置。此時不能根據函數getName運行時的位置來判斷,而是根據匿名函數的運行時位置來判斷。
function (){ return this.name; };
顯然,匿名函數所在的對象是window,因此this的安身之處定然在window,則匿名函數返回的this.name其實是window.name,因此alert出來的就是“全局”!
那麼,如何在閉包中使得this身處在xpg中呢?—緩存this
var name="全局"; var xpg={ name:"局部", getName:function(){ var that=this; return function(){ return that.name; }; } }; alert(xpg.getName()());
在getName函數中定義that=this,此時getName函數運行時位置在
alert(xpg.getName());
則this指向xpg對象,因此that也指向xpg對象。在閉包的匿名函數中返回that.name,則此時返回的that.name其實是xpg.name,因此就可以alert出來 “局部”!
3、new關鍵字創建新對象
new關鍵字後的構造函數中的this指向用該構造函數構造出來的新對象:
function Person(__name){ this.name = __name; //這個this指向用該構造函數構造的新對象,這個例子是Bob對象 } Person.prototype.show = function(){ alert(this.name); //this 指向Person,this.name = Person.name; } var Bob = new Person("Bob"); Bob.show(); //Bob
4、call與apply中的this
在JavaScript中能管的住this的估計也就非call與apply莫屬了。
call與apply就像this的父母一般,讓this住哪它就得住哪,不得不聽話!當無參數時,當前對象為window
var name="全局"; var xpg={ name:"局部" }; function getName(){ alert(this.name); } getName(xpg); getName.call(xpg); getName.call();
其中this身處函數getName中。無論this身處何處,一定要找到函數運行時的位置。此時函數getName運行時的位置
getName(xpg);
顯然,函數getName所在的對象是window,因此this的安身之處定然在window,即指向window對象,則getName返回的this.name其實是window.name,因此alert出來的是“全局”!
那麼,該call與apply登場了,因為this必須聽他們的指揮!
getName.call(xpg);
其中,call指定this的安身之處就是在xpg對象,因為this被迫只能在xpg那安家,則此時this指向xpg對象, this.name其實是xpg.name,因此alert出來的是“局部”!
5、eval中的this
對於eval函數,其執行時候似乎沒有指定當前對象,但實際上其this並非指向window,因為該函數執行時的作用域是當前作用域,即等同於在該行將裡面的代碼填進去。下面的例子說明了這個問題:
var name = "window"; var Bob = { name: "Bob", showName: function(){ eval("alert(this.name)"); } }; Bob.showName(); //Bob
6、沒有明確的當前對象時的this
當沒有明確的執行時的當前對象時,this指向全局對象window。
例如對於全局變量引用的函數上我們有:
var name = "Tom"; var Bob = { name: "Bob", show: function(){ alert(this.name); } } var show = Bob.show; show(); //Tom
你可能也能理解成show是window對象下的方法,所以執行時的當前對象時window。但局部變量引用的函數上,卻無法這麼解釋:
var name = "window"; var Bob = { name: "Bob", showName: function(){ alert(this.name); } }; var Tom = { name: "Tom", showName: function(){ var fun = Bob.showName; fun(); } }; Tom.showName(); //window
在浏覽器中setTimeout、setInterval和匿名函數執行時的當前對象是全局對象window,這條我們可以看成是上一條的一個特殊情況。
var name = "Bob"; var nameObj ={ name : "Tom", showName : function(){ alert(this.name); }, waitShowName : function(){ setTimeout(this.showName, 1000); } }; nameObj.waitShowName();
所以在運行this.showName的時候,this指向了window,所以最後顯示了window.name。
7、dom事件中的this
(1)你可以直接在dom元素中使用
<input id="btnTest" type="button" value="提交" onclick="alert(this.value))" />
分析:對於dom元素的一個onclick(或其他如onblur等)屬性,它為所屬的html元素所擁有,直接在它觸發的函數裡寫this,this應該指向該html元素。
(2)給dom元素注冊js函數
a、不正確的方式
<script type="text/javascript"> function thisTest(){ alert(this.value); // 彈出undefined, this在這裡指向?? } </script> <input id="btnTest" type="button" value="提交" onclick="thisTest()" />
分析:onclick事件直接調用thisTest函數,程序就會彈出undefined。因為thisTest函數是在window對象中定義的,
所以thisTest的擁有者(作用域)是window,thisTest的this也是window。而window是沒有value屬性的,所以就報錯了。
b、正確的方式
<input id="btnTest" type="button" value="提交" /> <script type="text/javascript"> function thisTest(){ alert(this.value); } document.getElementById("btnTest").onclick=thisTest; //給button的onclick事件注冊一個函數 </script>
分析:在前面的示例中,thisTest函數定義在全局作用域(這裡就是window對象),所以this指代的是當前的window對象。而通過document.getElementById(“btnTest”).onclick=thisTest;這樣的形式,其實是將btnTest的onclick屬性設置為thisTest函數的一個副本,在btnTest的onclick屬性的函數作用域內,this歸btnTest所有,this也就指向了btnTest。其實如果有多個dom元素要注冊該事件,我們可以利用不同的dom元素id,用下面的方式實現:
document.getElementById("domID").onclick=thisTest; //給button的onclick事件注冊一個函數。
因為多個不同的HTML元素雖然創建了不同的函數副本,但每個副本的擁有者都是相對應的HTML元素,各自的this也都指向它們的擁有者,不會造成混亂。
為了驗證上述說法,我們改進一下代碼,讓button直接彈出它們對應的觸發函數:
<input id="btnTest1" type="button" value="提交1" onclick="thisTest()" /> <input id="btnTest2" type="button" value="提交2" /> <script type="text/javascript"> function thisTest(){ this.value="提交中"; } var btn=document.getElementById("btnTest1"); alert(btn.onclick); //第一個按鈕函數 var btnOther=document.getElementById("btnTest2"); btnOther.onclick=thisTest; alert(btnOther.onclick); //第二個按鈕函數 </script> 其彈出的結果是: //第一個按鈕 function onclick(){ thisTest() } //第二個按鈕 function thisTest(){ this.value="提交中"; }
從上面的結果你一定理解的更透徹了。
By the way,每新建一個函數的副本,程序就會為這個函數副本分配一定的內存。而實際應用中,大多數函數並不一定會被調用,於是這部分內存就被白白浪費了。所以我們通常都這麼寫:
<input id="btnTest1" type="button" value="提交1" onclick="thisTest(this)" /> <input id="btnTest2" type="button" value="提交2" onclick="thisTest(this)" /> <input id="btnTest3" type="button" value="提交3" onclick="thisTest(this)" /> <input id="btnTest4" type="button" value="提交4" onclick="thisTest(this)" /> <script type="text/javascript"> function thisTest(obj){ alert(obj.value); } </script>
這是因為我們使用了函數引用的方式,程序就只會給函數的本體分配內存,而引用只分配指針。這樣寫一個函數,調用的地方給它分配一個(指針)引用,這樣效率就高很多。當然,如果你覺得這樣注冊事件不能兼容多種浏覽器,可以寫下面的注冊事件的通用腳本:
//js事件 添加 EventUtil.addEvent(dom元素,事件名稱,事件觸發的函數名) 移除EventUtil.removeEvent(dom元素,事件名稱,事件觸發的函數名) var EventUtil = new eventManager(); //js事件通用管理器 dom元素 添加或者移除事件 function eventManager() { //添加事件 //oDomElement:dom元素,如按鈕,文本,document等; ****** oEventType:事件名稱(如:click,如果是ie浏覽器,自動將click轉換為onclick);****** oFunc:事件觸發的函數名 this.addEvent = function(oDomElement, oEventType, oFunc) { //ie if (oDomElement.attachEvent) { oDomElement.attachEvent("on" + oEventType, oFunc); } //ff,opera,safari等 else if (oDomElement.addEventListener) { oDomElement.addEventListener(oEventType, oFunc, false); } //其他 else { oDomElement["on" + oEventType] = oFunc; } } this.removeEvent = function(oDomElement, oEventType, oFunc) { //ie if (oDomElement.detachEvent) { oDomElement.detachEvent("on" + oEventType, oFunc); } //ff,opera,safari等 else if (oDomElement.removeEventListener) { oDomElement.removeEventListener(oEventType, oFunc, false); } //其他 else { oDomElement["on" + oEventType] = null; } } }
正像注釋寫的那樣,要注冊dom元素事件,用EventUtil.addEvent(dom元素,事件名稱,事件觸發的函數名)即可, 移除時可以這樣寫:EventUtil.removeEvent(dom元素,事件名稱,事件觸發的函數名),這是題外話,不說了。
以上就是本文的全部內容,希望通過這篇文章大家更加了解javascript的this關鍵字,大家共同進步。