而在 JavaScript 中,this 是動(dòng)態(tài)綁定,或稱為運(yùn)行期綁定的,這就致使 JavaScript 中的 this 關(guān)鍵字有能力具有多重含義,帶來靈活性的同時(shí),也為初學(xué)者帶來很多困惑。本文僅就這1問題展開討論,閱罷本文,讀者若能正確回答 JavaScript 中的 What ’s this 問題,作為作者,我就會(huì)覺得花費(fèi)這么多工夫,撰寫這樣1篇文章是值得的。
我們要記住1句話:this永久指向函數(shù)運(yùn)行時(shí)所在的對(duì)象!而不是函數(shù)被創(chuàng)建時(shí)所在的對(duì)象。也即:誰(shuí)調(diào)用,指向誰(shuí)。切記…
本文將分3種情況來分析this對(duì)象到底身處何方。
不管this身處何處,第1要?jiǎng)?wù)就是要找到函數(shù)運(yùn)行時(shí)的位置。
var name="全局";
function getName(){
var name="局部";
return this.name;
};
alert(getName());
當(dāng)this出現(xiàn)在全局環(huán)境的函數(shù)getName中時(shí),此時(shí)函數(shù)getName運(yùn)行時(shí)的位置在
alert(getName());
明顯,函數(shù)getName所在的對(duì)象是全局對(duì)象,即window,因此this的安身的地方定然在window。此時(shí)的this指向window對(duì)象,則getName返回的this.name實(shí)際上是window.name,因此alert出來的是“全局”!
那末,當(dāng)this不是出現(xiàn)在全局環(huán)境的函數(shù)中,而是出現(xiàn)在局部環(huán)境的函數(shù)中時(shí),又會(huì)身陷何方呢?
var name="全局";
var xpg={
name:"局部",
getName:function(){
return this.name;
}
};
alert(xpg.getName());
其中this身處的函數(shù)getName不是在全局環(huán)境中,而是處在xpg環(huán)境中。不管this身處何處,1定要找到函數(shù)運(yùn)行時(shí)的位置。此時(shí)函數(shù)getName運(yùn)行時(shí)的位置
alert(xpg.getName());
明顯,函數(shù)getName所在的對(duì)象是xpg,因此this的安身的地方定然在xpg,即指向xpg對(duì)象,則getName返回的this.name實(shí)際上是xpg.name,因此alert出來的是“局部”!
舉個(gè)例子鞏固1下:
var someone = {
name: "Bob",
showName: function(){
alert(this.name);
}
};
var other = {
name: "Tom",
showName: someone.showName
}
other.showName(); //Tom
this關(guān)鍵字雖然是在someone.showName中聲明的,但運(yùn)行的時(shí)候是other.showName,所以this指向other.showName函數(shù)確當(dāng)前對(duì)象,即other,故最后alert出來的是other.name。
閉包也是個(gè)不安份子,本文暫且不對(duì)其過于贅述,簡(jiǎn)而言之:所謂閉包就是在1個(gè)函數(shù)內(nèi)部創(chuàng)建另外一個(gè)函數(shù),且內(nèi)部函數(shù)訪問了外部的變量。
浪子this與痞子閉包混在1起,可見將永毋寧日啊!
var name="全局";
var xpg={
name:"局部",
getName:function(){
return function(){
return this.name;
};
}
};
alert(xpg.getName()());
此時(shí)的this明顯身處窘境,居然處在getName函數(shù)中的匿名函數(shù)里面,而該匿名函數(shù)又調(diào)用了變量name,因此構(gòu)成了閉包,即this身處閉包中。
不管this身處何處,1定要找到函數(shù)運(yùn)行時(shí)的位置。此時(shí)不能根據(jù)函數(shù)getName運(yùn)行時(shí)的位置來判斷,而是根據(jù)匿名函數(shù)的運(yùn)行時(shí)位置來判斷。
function (){
return this.name;
};
明顯,匿名函數(shù)所在的對(duì)象是window,因此this的安身的地方定然在window,則匿名函數(shù)返回的this.name實(shí)際上是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函數(shù)中定義that=this,此時(shí)getName函數(shù)運(yùn)行時(shí)位置在
alert(xpg.getName());
則this指向xpg對(duì)象,因此that也指向xpg對(duì)象。在閉包的匿名函數(shù)中返回that.name,則此時(shí)返回的that.name實(shí)際上是xpg.name,因此就能夠alert出來 “局部”!
new關(guān)鍵字后的構(gòu)造函數(shù)中的this指向用該構(gòu)造函數(shù)構(gòu)造出來的新對(duì)象:
function Person(__name){
this.name = __name; //這個(gè)this指向用該構(gòu)造函數(shù)構(gòu)造的新對(duì)象,這個(gè)例子是Bob對(duì)象
}
Person.prototype.show = function(){
alert(this.name); //this 指向Person,this.name = Person.name;
}
var Bob = new Person("Bob");
Bob.show(); //Bob
在JavaScript中能管的住this的估計(jì)也就非call與apply莫屬了。
call與apply就像this的父母1般,讓this住哪它就得住哪,不能不聽話!當(dāng)無(wú)參數(shù)時(shí),當(dāng)前對(duì)象為window
var name="全局";
var xpg={
name:"局部"
};
function getName(){
alert(this.name);
}
getName(xpg);
getName.call(xpg);
getName.call();
其中this身處函數(shù)getName中。不管this身處何處,1定要找到函數(shù)運(yùn)行時(shí)的位置。此時(shí)函數(shù)getName運(yùn)行時(shí)的位置
getName(xpg);
明顯,函數(shù)getName所在的對(duì)象是window,因此this的安身的地方定然在window,即指向window對(duì)象,則getName返回的this.name實(shí)際上是window.name,因此alert出來的是“全局”!
那末,該call與apply登場(chǎng)了,由于this必須聽他們的指揮!
getName.call(xpg);
其中,call指定this的安身的地方就是在xpg對(duì)象,由于this被迫只能在xpg那安家,則此時(shí)this指向xpg對(duì)象, this.name實(shí)際上是xpg.name,因此alert出來的是“局部”!
對(duì)eval函數(shù),其履行時(shí)候仿佛沒有指定當(dāng)前對(duì)象,但實(shí)際上其this并不是指向window,由于該函數(shù)履行時(shí)的作用域是當(dāng)前作用域,即同等于在該即將里面的代碼填進(jìn)去。下面的例子說明了這個(gè)問題:
var name = "window";
var Bob = {
name: "Bob",
showName: function(){
eval("alert(this.name)");
}
};
Bob.showName(); //Bob
當(dāng)沒有明確的履行時(shí)確當(dāng)前對(duì)象時(shí),this指向全局對(duì)象window。
例如對(duì)全局變量援用的函數(shù)上我們有:
var name = "Tom";
var Bob = {
name: "Bob",
show: function(){
alert(this.name);
}
}
var show = Bob.show;
show(); //Tom
你可能也能理解成show是window對(duì)象下的方法,所以履行時(shí)確當(dāng)前對(duì)象時(shí)window。但局部變量援用的函數(shù)上,卻沒法這么解釋:
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和匿名函數(shù)履行時(shí)確當(dāng)前對(duì)象是全局對(duì)象window,這條我們可以看成是上1條的1個(gè)特殊情況。
var name = "Bob";
var nameObj ={
name : "Tom",
showName : function(){
alert(this.name);
},
waitShowName : function(){
setTimeout(this.showName, 1000);
}
};
nameObj.waitShowName();
所以在運(yùn)行this.showName的時(shí)候,this指向了window,所以最后顯示了window.name。
(1)你可以直接在dom元素中使用
<input id="btnTest" type="button" value="提交" onclick="alert(this.value))" />
分析:對(duì)dom元素的1個(gè)onclick(或其他如onblur等)屬性,它為所屬的html元素所具有,直接在它觸發(fā)的函數(shù)里寫this,this應(yīng)當(dāng)指向該html元素。
(2)給dom元素注冊(cè)js函數(shù)
a、不正確的方式
<script type="text/javascript">
function thisTest(){
alert(this.value); // 彈出undefined, this在這里指向??
}
</script>
<input id="btnTest" type="button" value="提交" onclick="thisTest()" />
分析:onclick事件直接調(diào)用thisTest函數(shù),程序就會(huì)彈出undefined。由于thisTest函數(shù)是在window對(duì)象中定義的,
所以thisTest的具有者(作用域)是window,thisTest的this也是window。而window是沒有value屬性的,所以就報(bào)錯(cuò)了。
b、正確的方式
<input id="btnTest" type="button" value="提交" />
<script type="text/javascript">
function thisTest(){
alert(this.value);
}
document.getElementById("btnTest").onclick=thisTest; //給button的onclick事件注冊(cè)1個(gè)函數(shù)
</script>
分析:在前面的示例中,thisTest函數(shù)定義在全局作用域(這里就是window對(duì)象),所以this指代的是當(dāng)前的window對(duì)象。而通過document.getElementById(“btnTest”).onclick=thisTest;這樣的情勢(shì),實(shí)際上是將btnTest的onclick屬性設(shè)置為thisTest函數(shù)的1個(gè)副本,在btnTest的onclick屬性的函數(shù)作用域內(nèi),this歸btnTest所有,this也就指向了btnTest。其實(shí)如果有多個(gè)dom元素要注冊(cè)該事件,我們可以利用不同的dom元素id,用下面的方式實(shí)現(xiàn):
document.getElementById("domID").onclick=thisTest; //給button的onclick事件注冊(cè)1個(gè)函數(shù)。
由于多個(gè)不同的HTML元素雖然創(chuàng)建了不同的函數(shù)副本,但每一個(gè)副本的具有者都是相對(duì)應(yīng)的HTML元素,各自的this也都指向它們的具有者,不會(huì)造成混亂。
為了驗(yàn)證上陳述法,我們改進(jìn)1下代碼,讓button直接彈出它們對(duì)應(yīng)的觸發(fā)函數(shù):
<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); //第1個(gè)按鈕函數(shù)
var btnOther=document.getElementById("btnTest2");
btnOther.onclick=thisTest;
alert(btnOther.onclick); //第2個(gè)按鈕函數(shù)
</script>
其彈出的結(jié)果是:
//第1個(gè)按鈕
function onclick(){
thisTest()
}
//第2個(gè)按鈕
function thisTest(){
this.value="提交中";
}
從上面的結(jié)果你1定理解的更透徹了。
By the way,每新建1個(gè)函數(shù)的副本,程序就會(huì)為這個(gè)函數(shù)副本分配1定的內(nèi)存。而實(shí)際利用中,大多數(shù)函數(shù)其實(shí)不1定會(huì)被調(diào)用,因而這部份內(nèi)存就被白白浪費(fèi)了。所以我們通常都這么寫:
<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>
這是由于我們使用了函數(shù)援用的方式,程序就只會(huì)給函數(shù)的本體分配內(nèi)存,而援用只分配指針。這樣寫1個(gè)函數(shù),調(diào)用的地方給它分配1個(gè)(指針)援用,這樣效力就高很多。固然,如果你覺得這樣注冊(cè)事件不能兼容多種閱讀器,可以寫下面的注冊(cè)事件的通用腳本:
//js事件 添加 EventUtil.addEvent(dom元素,事件名稱,事件觸發(fā)的函數(shù)名) 移除EventUtil.removeEvent(dom元素,事件名稱,事件觸發(fā)的函數(shù)名)
var EventUtil = new eventManager();
//js事件通用管理器 dom元素 添加或移除事件
function eventManager() {
//添加事件
//oDomElement:dom元素,如按鈕,文本,document等; ****** oEventType:事件名稱(如:click,如果是ie閱讀器,自動(dòng)將click轉(zhuǎn)換為onclick);****** oFunc:事件觸發(fā)的函數(shù)名
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;
}
}
}
正像注釋寫的那樣,要注冊(cè)dom元素事件,用EventUtil.addEvent(dom元素,事件名稱,事件觸發(fā)的函數(shù)名)便可, 移除時(shí)可以這樣寫:EventUtil.removeEvent(dom元素,事件名稱,事件觸發(fā)的函數(shù)名)。這是題外話,不說了。