Quiz
请看下面的代码,最后alert出来的是什么呢?
1 var name = "Bob"; 2 var nameObj ={ 3 name : "Tom", 4 showName : function(){ 5 alert(this.name); 6 }, 7 waitShowName : function(){ 8 setTimeout(this.showName, 1000); 9 } 10 }; 11 12 nameObj.waitShowName(); //Bob
要解决这个问题我们需要了解Javascript的this关键字的用法。
this指向哪里?
一般而言,在Javascript中,this指向函数执行时的当前对象。
In JavaScript, as in most object-oriented programming languages,
this
is a special keyword that is used within methods to refer to the object on which a method is being invoked.——jQuery Fundamentals (Chapter 2), by Rebecca Murphey
值得注意,该关键字在Javascript中和执行环境,而非声明环境有关。
The this keyword is relative to the execution context, not the declaration context.
我们举个例子来说明这个问题:
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。
没有明确的当前对象时
当没有明确的执行时的当前对象时,this指向全局对象window。
By default,
this
refers to the global object.为什么说是全局对象(the global object),因为非浏览器情况下(例如:nodejs)中全局变量并非window对象,而就是叫“全局变量”(the global object)。不过由于我们这片文章主要讨论的是前端开发知识,所以nodejs就被我们忽略了。
例如对于全局变量引用的函数上我们有:
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和匿名函数
文章开头的问题的答案是Bob。
在浏览器中setTimeout、setInterval和匿名函数执行时的当前对象是全局对象window,这条我们可以看成是上一条的一个特殊情况。
所以在运行this.showName的时候,this指向了window,所以最后显示了window.name。
浏览器中全局变量可以当成是window对象下的变量,例如全局变量a,可以用window.a来引用。
我们将代码改成匿名函数可能更好理解一些:
var name = "Bob"; var nameObj ={ name : "Tom", showName : function(){ alert(this.name); }, waitShowName : function(){ function(__callback){ __callback(); }(this.showName); } }; nameObj.waitShowName(); //Bob
在调用nameObj.waitShowName时候,我们运行了一个匿名函数,将nameObj.showName作为回调函数传进这个匿名函数,然后匿名函数运行时,运行这个回调函数。由于匿名函数的当前对象是window,所以当在该匿名函数中运行回调函数时,回调函数的this指向了window,所以alert出来window.name。
由此看来setTimeout可以看做是一个延迟执行的:
function(__callback){ __callback(); }
setInterval也如此类比。
但如果我们的确想得到的回答是Tom呢?通过一些技巧,我们能够得到想要的答案:
var name = "Bob"; var nameObj ={ name : "Tom", showName : function(){ alert(this.name); }, waitShowName : function(){ var that = this; setTimeout(function(){ that.showName(); }, 1000); } }; nameObj.waitShowName(); //Tom
在执行nameObj.waitShowName函数时,我们先对其this赋给变量that(这是为了避免setTimeout中的匿名函数运行时,匿名函数中的this指向window),然后延迟运行匿名函数,执行that.showName,即nameObj.showName,所以alert出正确结果Tom。
eval
对于eval函数,其执行时候似乎没有指定当前对象,但实际上其this并非指向window,因为该函数执行时的作用域是当前作用域,即等同于在该行将里面的代码填进去。下面的例子说明了这个问题:
var name = "window"; var Bob = { name: "Bob", showName: function(){ eval("alert(this.name)"); } }; Bob.showName(); //Bob
apply和call
apply和call能够强制改变函数执行时的当前对象,让this指向其他对象。因为apply和call较为类似,所以我们以apply为例:
var name = "window"; var someone = { name: "Bob", showName: function(){ alert(this.name); } }; var other = { name: "Tom" }; someone.showName.apply(); //window someone.showName.apply(other); //Tom
apply用于改变函数执行时的当前对象,当无参数时,当前对象为window,有参数时当前对象为该参数。于是这个例子Bob成功偷走了Tom的名字。
new关键字
new关键字后的构造函数中的this指向用该构造函数构造出来的新对象:
function Person(__name){ this.name = __name; //这个this指向用该构造函数构造的新对象,这个例子是Bob对象 } Person.prototype.show = function(){ alert(this.name); } var Bob = new Person("Bob"); Bob.show(); //Bob
思考题
1. 请问下面代码会alert出什么,为什么?
var name = "Bob"; var nameObj ={ name : "Tom", showName : function(){ alert(this.name); }, waitShowName : function(){ var that = this; setTimeout("that.showName();", 1000); } }; nameObj.waitShowName();
2. 请问下面代码会alert出什么,为什么?
var fun = new Function("alert(this)"); fun();
3. 下面代码分别在IE和其他浏览器上运行有什么差异,可以用什么方法解决这个差异问题?
IE:
<button id = "box" name = "box">Click Me!button> <script> var name = "window"; function showName(){ alert(this.name); } document.getElementById("box").attachEvent("onclick", showName); script>
Others:
<button id = "box" name = "box">Click Me!button> <script> var name = "window"; function showName(){ alert(this.name); } document.getElementById("box").addEventListener("click", showName, false); script>
参考文献
Javascript Closures . Richard Cornford . March 2004
Javascript的this用法 . 阮一峰 . 2010.4.30
#1楼 林J 2012-11-01 08:59
#2楼 bluescreen 2012-11-01 09:01
是不是该这样呢!
var
name =
"Bob"
;
var
nameObj ={
name :
"Tom"
,
showName :
function
(){
alert(
this
.name);
},
waitShowName :
function
(){
var
that =
this
;
setTimeout(that.showName(), 1000);
}
};
nameObj.waitShowName();
#3楼[楼主] Justany_WhiteSnow 2012-11-01 09:16
引用1.第一个思考题你代码是不是错了? setTimeout("that.showName();", 1000); 这句会报错吧!如有不对指出还望谅解指证!谢谢!
是不是该这样呢!
答案就是会报错,有兴趣的话考虑下为什么吧。
哦,刚没看你的代码。你的代码是没什么意义的。因为that.showName在传进setTimeout之前已经执行了。
#4楼 bluescreen 2012-11-01 09:29
引用@bluescreen
引用引用1.第一个思考题你代码是不是错了? setTimeout("that.showName();", 1000); 这句会报错吧!如有不对指出还望谅解指证!谢谢!
是不是该这样呢!
答案就是会报错,有兴趣的话考虑下为什么吧。
哦,刚没看你的代码。你的代码是没什么意义的。因为that.showName在传进setTimeout之前已经执行了。
对!是that.showName在传进setTimeout之前已经执行了。无论时间如何设置都会!
#5楼 林J 2012-11-01 09:32
会报错的原因是因为setTimeout对象是windows,相当于执行的是window.that.showName(),这里that没有定义?
这样的话这个题目没什么意义啊。我是想看如何用setTimeout弹出Tom的。。
#6楼[楼主] Justany_WhiteSnow 2012-11-01 09:37
引用@Justany_WhiteSnow
会报错的原因是因为setTimeout对象是windows,相当于执行的是window.that.showName(),这里that没有定义?
这样的话这个题目没什么意义啊。我是想看如何用setTimeout弹出Tom的。。
弹出Tom的方法,原文有啊。
也不能说没有意义。思考题1是为了对比:
setTimeout(函数名, 延迟)
setTimeout(匿名函数, 延迟)
setTimeout(字符串代码, 延迟)
这是三种方法的差异的。
#7楼 林J 2012-11-01 09:45
哦对。顺便请教楼主一个问题啊:setTimeout(that.showName(),1000),setTimeout(that.showName,1000),
setTimeout("that.showName()",1000)这三种写法在解析的时候有什么区别呢?我只知道加‘()’的会直接执行,但是不知道解析器究竟干了什么。
#8楼[楼主] Justany_WhiteSnow 2012-11-01 10:02
引用@Justany_WhiteSnow
哦对。顺便请教楼主一个问题啊:setTimeout(that.showName(),1000),setTimeout(that.showName,1000),
setTimeout("that.showName()",1000)这三种写法在解析的时候有什么区别呢?我只知道加‘()’的会直接执行,但是不知道解析器究竟干了什么。
1.setTimeout(that.showName(),1000)
that.showName是函数引用,that.showName()是函数运行。这种传递方式真正传进去的是that.showName函数的返回值。
2.setTimeout(that.showName,1000)
个人理解是相当于一个延迟执行的
(function(__callback){
__callback();
})(that.showName);
这个我已在正文说明了。
3.setTimeout("that.showName()",1000)
相当于一个延迟执行的
(
new
Function(
"that.showName()"
))()
具体来说setTimeout(字符串代码, 延迟)是在一定延迟之后用字符串代码创建一个新的函数,因为该函数没有明确的当前对象,所以this指向全局变量。当然这里还有一个问题为什么that没法正常引用,因为他重新建立一个执行环境(context),且这个执行环境和原来执行环境无关。
我想思考题1和2的答案也很清晰了。
#9楼 技术屌丝 2012-11-01 10:27
#10楼 林J 2012-11-01 10:29
引用@林J
引用引用@Justany_WhiteSnow
哦对。顺便请教楼主一个问题啊:setTimeout(that.showName(),1000),setTimeout(that.showName,1000),
setTimeout("that.showName()",1000)这三种写法在解析的时候有什么区别呢?我只知道加‘()’的会直接执行,但是不知道解析器究竟干了什么。
1.setTimeout(that.showName(),1000)
that.showName是函数引用,that.showName()是函数运行。这种传递方式真正传进去的是that.sho...
感谢回答,第三种用字符串代码创建一个新的函数,和eval方法有点像啊。我以前看过有文章说过最好不用使用eval方法的,因为它会执行任意传给它的代码,不管对错。一般情况下还是不要使用setTimeout(字符串代码, 延迟)好点吧。
#11楼 Coolicer 2012-11-01 10:36
var
name =
"Bob"
;
var
nameObj ={
name :
"Tom"
,
showName :
function
(){
alert(
this
.name);
},
waitShowName :
function
(){
var
that =
this
;
setTimeout(
"that.showName();"
, 1000);
//setTimeout("nameObj.showName();", 1000);
// 这里的写法应该相当于eval效果? 而setTimeout指向的this是window,
//所以window中没有showName这个方法,把that改成nameObj就可以弹出Tom
}
};
nameObj.waitShowName();
#12楼[楼主] Justany_WhiteSnow 2012-11-01 10:44
参见8楼。eval和new Function还是有一定差别的
#13楼 Coolicer 2012-11-01 10:58
New Function 文章好像没有吧,New Function ,with, eval,setTimeout都是比较奇怪的东东,看着还真会晕。
#14楼 Iyanzi 2012-11-01 15:45
#15楼 松下裤腰带 2012-11-01 20:22
#16楼 PEPE YU 2012-11-02 16:48
show(); //Tom
==>
Bob.show()//Bob
#17楼 PEPE YU 2012-11-02 17:00
#18楼 NinoFocus 2012-11-08 17:22
attachEvent的回调函数中的this关键字指向了window对象,而不是当前元素(IE的一个巨大缺点)。
addEventListener的回调函数中的this关键字指向了当前被点击的元素。
具体的可以看这篇文章: http://www.cnblogs.com/ninofocus/archive/2012/11/07/javascript-event-bind.html
#19楼 翱翔软件 2012-11-15 11:21
还有,
在开发时,可以采用两种方式,一种是命名空间的方式,一种是面向对象的方式。
#20楼 Albert_1 2012-12-25 10:15
var nameObj ={
name : "Tom",
showName : function(){
alert(this.name);
},
waitShowName : function(){
// var nameObj = this;
setTimeout("nameObj.showName();", 1000);
}
};
nameObj.waitShowName();
#21楼 Mr Code 2013-01-31 10:54
引用@Justany_WhiteSnow
会报错的原因是因为setTimeout对象是windows,相当于执行的是window.that.showName(),这里that没有定义?
这样的话这个题目没什么意义啊。我是想看如何用setTimeout弹出Tom的。。
弹出tom:
var
name =
"Bob"
;
var
nameObj ={
name :
"Tom"
,
showName :
function
(){
alert(
this
.name);
},
waitShowName :
function
(){
var
that =
this
;
setTimeout(
function
(){that.showName();}, 1000);
//setTimeout("nameObj.showName();", 1000);
// 这里的写法应该相当于eval效果? 而setTimeout指向的this是window,
//所以window中没有showName这个方法,把that改成nameObj就可以弹出Tom
}
};
nameObj.waitShowName();
#22楼 早起的菜鸟 2013-07-12 15:59
#23楼 snov 2015-01-16 00:50
第二个例子我觉得比较典型。但是结果为什么是这样呢。我想谈下自己的看法。
例子1
var name = "window";
var Bob = {
name: "Bob",
showName: function(){
alert(this.name);
}
};
var Tom = {
name: "Tom",
showName: function(){
Bob.showName();
}
};
Tom.showName(); //Bob
因为showName()函数的对象是Bob,所以this是指的Bob;
例子二
var name = "window";
var Bob = {
name: "Bob",
showName: function(){
alert(this.name);
}
};
var fun = Bob.showName;
fun(); //window
Bob.showName的函数赋值给fun,由于Bob是由匿名类构造的,赋值的也只是函数,及回调函数,本身是没有对象的。所以showName中的this就没有对象了,没有对象时就是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
给fun赋值为Bob.showName时,因为fun本身是临时变量(或者叫私有变量),也就没有所属对象;因为Bob.showName赋值的是函数,相当于回调函数,是没有对象的,fun和Bob.showName回调都没有对象,所以没有对象时还是window。
在看下面这种情况,
var name = "window";
var Bob = {
name: "Bob",
showName: function(){
alert(this.name);
}
};
var Tom = {
name: "Tom",
fun: undefined,
showName: function(){
this.fun = Bob.showName;
this.fun();
}
};
Tom.showName(); //Tom
虽然赋值的Bob.showName是回调函数,没有对象,但fun是有所属对象的就是Tom,所以结果是Tom。如果按你的说法"没有明确的当前对象时",this首先是Bob里,然后又是Tom里,不太明确,推理结果是window,这显然是和实际运行不符的。
本人c++程序员,初学javascript
#24楼 孙可瘦 2015-05-30 15:39
这样才会弹出tom对吧~