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();
要解决这个问题我们需要了解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.
值得注意,该关键字在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
Justany_WhiteSnow
关注 - 0
粉丝 - 269
17
0
(请您对文章做出评价)
» 下一篇:关联数据入门——RDF
- 分类: Javascript
- 标签: Javascript, web
-
#1楼 林J 2012-11-01
08:59支持一记,很有用。
-
#2楼 bluescreen 2012-11-01
09:011.第一个思考题你代码是不是错了? setTimeout("that.showName();", 1000); 这句会报错吧!如有不对指出还望谅解指证!谢谢!
是不是该这样呢!
12
3
4
5
6
7
8
9
10
11
12
13
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@bluescreen
引用
1.第一个思考题你代码是不是错了? setTimeout("that.showName();", 1000); 这句会报错吧!如有不对指出还望谅解指证!谢谢!
是不是该这样呢!答案就是会报错,有兴趣的话考虑下为什么吧。
哦,刚没看你的代码。你的代码是没什么意义的。因为that.showName在传进setTimeout之前已经执行了。
-
#4楼 bluescreen 2012-11-01
09:29@Justany_WhiteSnow
引用
@bluescreen
引用引用1.第一个思考题你代码是不是错了? setTimeout("that.showName();", 1000); 这句会报错吧!如有不对指出还望谅解指证!谢谢!
是不是该这样呢!答案就是会报错,有兴趣的话考虑下为什么吧。
哦,刚没看你的代码。你的代码是没什么意义的。因为that.showName在传进setTimeout之前已经执行了。
对!是that.showName在传进setTimeout之前已经执行了。无论时间如何设置都会!
-
#5楼 林J 2012-11-01
09:32@Justany_WhiteSnow
会报错的原因是因为setTimeout对象是windows,相当于执行的是window.that.showName(),这里that没有定义?
这样的话这个题目没什么意义啊。我是想看如何用setTimeout弹出Tom的。。 -
#6楼[楼主] Justany_WhiteSnow 2012-11-01
09:37@林J
引用
@Justany_WhiteSnow
会报错的原因是因为setTimeout对象是windows,相当于执行的是window.that.showName(),这里that没有定义?
这样的话这个题目没什么意义啊。我是想看如何用setTimeout弹出Tom的。。
弹出Tom的方法,原文有啊。
也不能说没有意义。思考题1是为了对比:
setTimeout(函数名, 延迟)
setTimeout(匿名函数, 延迟)
setTimeout(字符串代码, 延迟)
这是三种方法的差异的。 -
#7楼 林J 2012-11-01
09:45@Justany_WhiteSnow
哦对。顺便请教楼主一个问题啊:setTimeout(that.showName(),1000),setTimeout(that.showName,1000),
setTimeout("that.showName()",1000)这三种写法在解析的时候有什么区别呢?我只知道加‘()’的会直接执行,但是不知道解析器究竟干了什么。 -
#8楼[楼主] Justany_WhiteSnow 2012-11-01
10:02@林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.showName函数的返回值。
2.setTimeout(that.showName,1000)
个人理解是相当于一个延迟执行的
12
3
(function(__callback){
__callback();
})(that.showName);
这个我已在正文说明了。
3.setTimeout("that.showName()",1000)
相当于一个延迟执行的
1(
new
Function(
"that.showName()"
))()
具体来说setTimeout(字符串代码, 延迟)是在一定延迟之后用字符串代码创建一个新的函数,因为该函数没有明确的当前对象,所以this指向全局变量。当然这里还有一个问题为什么that没法正常引用,因为他重新建立一个执行环境(context),且这个执行环境和原来执行环境无关。
我想思考题1和2的答案也很清晰了。
-
#9楼 技术屌丝 2012-11-01
10:27已懵圈 @_@
-
#10楼 林J 2012-11-01
10:29@Justany_WhiteSnow
引用
@林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
12
3
4
5
6
7
8
9
10
11
12
13
14
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@Coolicer
参见8楼。eval和new Function还是有一定差别的 -
#13楼 Coolicer 2012-11-01
10:58@Justany_WhiteSnow
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:48var show = Bob.show;
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:15var name = "Bob";
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@林J
引用
@Justany_WhiteSnow
会报错的原因是因为setTimeout对象是windows,相当于执行的是window.that.showName(),这里that没有定义?
这样的话这个题目没什么意义啊。我是想看如何用setTimeout弹出Tom的。。
弹出tom:
12
3
4
5
6
7
8
9
10
11
12
13
14
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<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script type="text/javascript">
var name = "Bob";
var that = null;
var nameObj ={
name : "Tom",
showName : function(){
alert(this.name);
},
waitShowName : function(){
that = this;
setTimeout("that.showName();", 1000);
}
};nameObj.waitShowName();
</script>
</head>
<body></body>
</html>这样才会弹出tom对吧~
深入浅出 JavaScript 中的 this
JavaScript 是一种脚本语言,因此被很多人认为是简单易学的。然而情况恰恰相反,JavaScript 支持函数式编程、闭包、基于原型的继承等高级功能。本文仅采撷其中的一例:JavaScript 中的 this 关键字,深入浅出的分析其在不同情况下的含义,形成这种情况的原因以及 Dojo 等 JavaScript 工具中提供的绑定 this 的方法。可以这样说,正确掌握了 JavaScript 中的 this 关键字,才算迈入了 JavaScript 这门语言的门槛。
10 评论:
王
群锋, 软件工程师, IBM
2012 年 7 月 09 日
在 IBM Bluemix 云平台上开发并部署您的下一个应用。
在 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 的含义是一样的,均指当前对象。
清单 1. Point.java
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 被自然绑定到该对象。
清单 2. point.js
var point = { x : 0, y : 0, moveTo : function(x, y) { this.x = this.x + x; this.y = this.y + y; } }; point.moveTo(1, 1)//this 绑定到当前对象,即 point 对象
作为函数调用
函数也可以直接被调用,此时 this 绑定到全局对象。在浏览器中,window 就是该全局对象。比如下面的例子:函数被调用时,this 被绑定到全局对象,接下来执行赋值语句,相当于隐式的声明了一个全局变量,这显然不是调用者希望的。
清单 3. nonsense.js
function makeNoSense(x) { this.x = x; } makeNoSense(5); x;// x 已经成为一个值为 5 的全局变量
对于内部函数,即声明在另外一个函数体内的函数,这种绑定到全局对象的方式会产生另外一个问题。我们仍然以前面提到的 point 对象为例,这次我们希望在 moveTo 方法内定义两个函数,分别将 x,y 坐标进行平移。结果可能出乎大家意料,不仅 point 对象没有移动,反而多出两个全局变量 x,y。
清单 4. point.js
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。
清单 5. point2.js
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 绑定到新创建的对象上。
清单 6. Point.js
function Point(x, y){ this.x = x; this.y = y; }
使用 apply 或 call 调用
让我们再一次重申,在 JavaScript 中函数也是对象,对象则有方法,apply 和 call 就是函数对象的方法。这两个方法异常强大,他们允许切换函数执行的上下文环境(context),即 this 绑定的对象。很多 JavaScript 中的技巧以及类库都用到了该方法。让我们看一个具体的例子:
清单 7. Point2.js
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 的含义,阅读到此已经足够了。如果大家不嫌枯燥,非要探寻其中究竟,那就一起迈入下一节吧。
函数的执行环境
JavaScript 中的函数既可以被当作普通函数执行,也可以作为对象的方法执行,这是导致 this 含义如此丰富的主要原因。一个函数被执行时,会创建一个执行环境(ExecutionContext),函数的所有的行为均发生在此执行环境中,构建该执行环境时,JavaScript 首先会创建 arguments
变量,其中包含调用函数时传入的参数。接下来创建作用域链。然后初始化变量,首先初始化函数的形参表,值为 arguments
变量中对应的值,如果 arguments
变量中没有对应值,则该形参初始化为 undefined
。如果该函数中含有内部函数,则初始化这些内部函数。如果没有,继续初始化该函数内定义的局部变量,需要注意的是此时这些变量初始化为 undefined
,其赋值操作在执行环境(ExecutionContext)创建成功后,函数执行时才会执行,这点对于我们理解
JavaScript 中的变量作用域非常重要,鉴于篇幅,我们先不在这里讨论这个话题。最后为 this
变量赋值,如前所述,会根据函数调用方式的不同,赋给 this
全局对象,当前对象等。至此函数的执行环境(ExecutionContext)创建成功,函数开始逐行执行,所需变量均从之前构建好的执行环境(ExecutionContext)中读取。
Function.bind
有了前面对于函数执行环境的描述,我们来看看 this 在 JavaScript 中经常被误用的一种情况:回调函数。JavaScript 支持函数式编程,函数属于一级对象,可以作为参数被传递。请看下面的例子 myObject.handler 作为回调函数,会在 onclick 事件被触发时调用,但此时,该函数已经在另外一个执行环境(ExecutionContext)中执行了,this 自然也不会绑定到 myObject 对象上。
清单 8. callback.js
button.onclick = myObject.handler;
这是 JavaScript 新手们经常犯的一个错误,为了避免这种错误,许多 JavaScript 框架都提供了手动绑定 this 的方法。比如 Dojo 就提供了 lang.hitch,该方法接受一个对象和函数作为参数,返回一个新函数,执行时 this 绑定到传入的对象上。使用 Dojo,可以将上面的例子改为:
清单 9. Callback2.js
button.onclick = lang.hitch(myObject, myObject.handler);
在新版的 JavaScript 中,已经提供了内置的 bind 方法供大家使用。
eval 方法
JavaScript 中的 eval 方法可以将字符串转换为 JavaScript 代码,使用 eval 方法时,this 指向哪里呢?答案很简单,看谁在调用 eval 方法,调用者的执行环境(ExecutionContext)中的 this 就被 eval 方法继承下来了。
结束语
本文介绍了 JavaScript 中的 this 关键字在各种情况下的含义,虽然这只是 JavaScript 中一个很小的概念,但借此我们可以深入了解 JavaScript 中函数的执行环境,而这是理解闭包等其他概念的基础。掌握了这些概念,才能充分发挥 JavaScript 的特点,才会发现 JavaScript 语言特性的强大。
javascript this用法小结
作者: 字体:[增加 减小] 类型:转载 时间:2008-12-19
this是JavaScript中功能最强大的关键字之一。不幸的是,如果你不知道它具体怎么工作,你将很难正确使用它。
this是面向对象语言中的一个重要概念,在JAVA,C#等大型语言中,this固定指向运行时的当前对象。但是在javascript中,由于 javascript的动态性(解释执行,当然也有简单的预编译过程),this的指向在运行时才确定。这个特性在给我们带来迷惑的同时也带来了编程上的 自由和灵活,结合apply(call)方法,可以使JS变得异常强大。
2.变化的this
在JavaScript中,this通常指向的是我们正在执行的函数本身,或者是指向该函数所属的对象(运行时)。当我们在页面中定义了函数 doSomething()的时候,它的owner是页面,或者是JavaScript中的window对象(或 global对象)。对于一个onclick属性,它为它所属的HTML元素所拥有,this应该指向该HTML元素。
2.1在几种常见场景中this的变化
函数示例
function doSomething ()
{
alert(this.navigator); //appCodeName
this.value = "I am from the Object constructor";
this.style.backgroundColor = "# 000000";
}
1. (A)作为普通函数直接调用时,this指向window对象.
2. (B)作为控件事件触发时
1) inline event registration 内联事件注册 .将事件直接写在HTML代码中(<element
onclick=”doSomething()”>), 此时this指向 window对象 。
2) Traditional event registration 传统事件注册 (DHTML方式).
形如 element.onclick = doSomething; 此时this指向 element对象
3) <element onclick=”doSomething(this)”>作为参数传递可以指向element
3. (C)作为对象使用时this指向当前对象。形如:new doSomething();
4. (D)使用apply 或者call方法时,this指向所传递的对象。
形如:var obj={}; doSomething.apply(obj,new Array(”nothing”); //thisàobj
下面我来阐述如何在事件处理中来使用this,之后我会附加一些this相关的例子。
Owner
接下来文章中我们将要讨论的问题是:在函数doSomething()中this所指的是什么?
Javascript代码
function doSomething() {
this.style.color = '#cc0000';
}
function doSomething() {
this.style.color = '#cc0000';
}
在JavaScript中,this通常指向的是我们正在执行的函数本身(译者注:用owner代表this所指向的内容),或者是,指向该函数所属的对象。当我们在页面中定义了函数doSomething()的时候,它的owner是页面,或者是JavaScript中的window对象(或 global对象)。对于一个onclick属性,它为它所属的HTML元素所拥有,this应该指向该HTML元素。
这种“所有权”就是JavaScript中面向对象的一种方式。在Objects as associative arrays中可以查看一些更多的信息。
如果我们在没有任何更多准备情况下执行doSomething(),this关键字会指向window,并且该函数试图改变window的 style.color。因为window并没有style对象,这个函数将非常不幸的运行失败,并产生JavaScript错误。
Copying
因此如果我们想充分利用this,我们不得不注意使用this的函数应该被正确的HTML元素所拥有。换句话说,我们应该复制这个函数到我们的onclick属性。Traditional event registration会关心它。
Javascript代码
element.onclick = doSomething;
element.onclick = doSomething;
这个函数被完整复制到onclick属性(现在成为了函数)。因此如果这个event handler被执行,this将指向HTML元素,并且该元素的颜色得以改变。
这种方法使得我们能够复制这个函数到多个event handler。每次this都将指向正确的HTML元素:
这样你就可以最大限度使用this。每当执行该函数,this所指向的HTML元素都正确响应事件,这些HTML元素拥有doSomething()的一个拷贝。
Referring
然而,如果你使用inline event registration(内联事件注册)
Javascript代码
<element onclick="doSomething()">
<element onclick="doSomething()">
你将不能拷贝该函数!反而这种差异是非常关键的。onclick属性并不包含实际的函数,仅仅是一个函数调用。
Javascript代码
doSomething();
doSomething();
因此,它将声明“转到doSomething()并且执行它”。当我们到达doSomething(),this关键字又重新指向了全局的window对象,函数返回错误信息。
The difference
如果你想使用this来指向HTML元素响应的事件,你必须确保this关键字被写在onclick属性里。只有在这种情况下它才指向event handler所注册的HTML元素。
Javascript代码
element.onclick = doSomething;
alert(element.onclick)
element.onclick = doSomething;
alert(element.onclick)
你将得到
Javascript代码
function doSomething() {
this.style.color = '#cc0000';
}
function doSomething() {
this.style.color = '#cc0000';
}
正如你所见,this关键字被展现在onclick函数中,因此它指向HTML元素。
但是如果执行
Javascript代码
<element onclick="doSomething()">
alert(element.onclick)
<element onclick="doSomething()">
alert(element.onclick)
你将得到
Javascript代码
function onclick() {
doSomething()
}
function onclick() {
doSomething()
}
这仅仅是到doSomething()函数的一个引用。this关键字并没有出现在onclick函数中,因此它不指向HTML元素。
例子--拷贝
下面的例子中,this被写入onclick函数里:
Javascript代码
element.onclick = doSomething
element.addEventListener('click', doSomething, false)
element.onclick = function() {this.style.color = '#cc0000';}
<element onclick="this.sytle.color = '#cc0000';">
element.onclick = doSomething
element.addEventListener('click', doSomething, false)
element.onclick = function() {this.style.color = '#cc0000';}
<element onclick="this.sytle.color = '#cc0000';">
例子--引用
下述情况中,this指向window:
Javascript代码
element.onclick = function() {doSomething()}
element.attachEvent('onclick', doSomething)
<element onclick="doSomething()">
element.onclick = function() {doSomething()}
element.attachEvent('onclick', doSomething)
<element onclick="doSomething()">
注意attachEvent()的出现。Microsoft event registration model最主要的弊端是attachEvent()创建了一个指向函数的引用,而不是复制它。因此有时不可能知道哪个HTML正在处理该事件。
组合使用
当使用内联事件注册时,你可以将this发送到函数以至于可以正常使用:
Javascript代码
<element onclick="doSomething(this)">
function doSomething(obj) {
//this出现在event handler中并被发送到函数
//obj指向HTML元素,因此可以这样:
obj.style.color = '#cc0000';
}
Javascript的this用法
作者: 阮一峰
日期: 2010年4月30日
this是Javascript语言的一个关键字。
它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。比如,
function test(){
this.x = 1;
}
随着函数使用场合的不同,this的值会发生变化。但是有一个总的原则,那就是this指的是,调用函数的那个对象。
下面分四种情况,详细讨论this的用法。
情况一:纯粹的函数调用
这是函数的最通常用法,属于全局性调用,因此this就代表全局对象Global。
请看下面这段代码,它的运行结果是1。
function test(){
this.x = 1;
alert(this.x);
}
test(); // 1
为了证明this就是全局对象,我对代码做一些改变:
var x = 1;
function test(){
alert(this.x);
}
test(); // 1
运行结果还是1。再变一下:
var x = 1;
function test(){
this.x = 0;
}
test();
alert(x); //0
情况二:作为对象方法的调用
函数还可以作为某个对象的方法调用,这时this就指这个上级对象。
function test(){
alert(this.x);
}
var o = {};
o.x = 1;
o.m = test;
o.m(); // 1
情况三 作为构造函数调用
所谓构造函数,就是通过这个函数生成一个新对象(object)。这时,this就指这个新对象。
function test(){
this.x = 1;
}
var o = new test();
alert(o.x); // 1
运行结果为1。为了表明这时this不是全局对象,我对代码做一些改变:
var x = 2;
function test(){
this.x = 1;
}
var o = new test();
alert(x); //2
运行结果为2,表明全局变量x的值根本没变。
情况四 apply调用
apply()是函数对象的一个方法,它的作用是改变函数的调用对象,它的第一个参数就表示改变后的调用这个函数的对象。因此,this指的就是这第一个参数。
var x = 0;
function test(){
alert(this.x);
}
var o={};
o.x = 1;
o.m = test;
o.m.apply(); //0
apply()的参数为空时,默认调用全局对象。因此,这时的运行结果为0,证明this指的是全局对象。
如果把最后一行代码修改为
o.m.apply(o); //1
运行结果就变成了1,证明了这时this代表的是对象o。
(完)
文档信息
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
- 发表日期: 2010年4月30日
- 更多内容: 档案 » JavaScript
- 购买文集: 《如何变得有思想》
- 社交媒体: twitter, weibo
- Feed订阅:
相关文章
- 2015.11.12: 读懂 ECMAScript
规格
一、概述 规格文件(specification)是计算机语言的官方标准,详细描述语法规则和实现方法。 - 2015.11.02: JavaScript 模块的循环加载
"循环加载"(circular dependency)指的是,a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本。 - 2015.06.30: ES6 的功能侦测库 ES-Checker
两周前,《ECMAScript国际标准(第6版)》正式通过,下一代 JavaScript 语言定案。 - 2015.06.08: 代码覆盖率工具 Istanbul 入门教程
测试的时候,我们常常关心,是否所有代码都测试到了。
广告(购买广告位)
留言(46条)
Shimu 说:
this平常用的时候有点混乱。不过this是遵循OO语言的,只要把这点想清楚就很好了~
Andy 说:
引用Shimu的发言:this平常用的时候有点混乱。不过this是遵循OO语言的,只要把这点想清楚就很好了~
记住一条:当function被作为method调用时,this指向调用对象。另外,JavaScript并不是OO的,而是object based的一种语言。之所以你会觉得this用起来混乱,是因为你还没理解JavaScript的诸如全局对象、event handler等一些机制。 所以,阮大侠写的“纯粹函数调用”其实是不“准确”的。
tun 说:
原来 var o = {};是在新建对象。
mead 说:
js看来博大精深啊.呵呵
真正学习,虽然非常讨厌javascript.没办法
天堂夜风 说:
JS中一切皆是对象。
是不是可以简单的理解为:
1. this指向的是被调用的对象,
2. 当子对象没有重新赋值,最终就是父对象中该属性的值。
netwjx 说:
this还可以通过call方法改变
(function(){ alert(this); }).call("hello world");
this 说:
this is a troublesome 'this'
JerryTang 说:
一直对javascript的this很困惑,以至于一直都尽量避免使用。现在终于理解到了。
looping 说:
真不错。通俗易懂~~~~~
wtz 说:
总结的很好。看懂了。谢谢
黑幕困兽 说:
this只能在函数内部使用,很明显不正确
this.x = 1;
alert(this.x);
这样使用this有错么?this此时指代window对象
tanvstan 说:
多谢博主的文章,我觉得是我看过的最好的javascript相关资料之一。
Yoya 说:
感觉JS里一些东西真的很混乱,里面的函数即要做爸(类)又要做儿子(类实例的方法)。比如下面的代码就让人感觉无比的混乱。
var x = 2;
function test()
{
this.x = 1;
}
var o = new test();
alert("o.x: " + o.x); //1
o.x = 3;
test();
x*=10;
alert("o.x: " + o.x); //3
alert("x: " + x); //10
Mr lang 说:
上面的那个很清晰啊,o.x是o对象的属性啊,而var x=2是window对象的属性啊,
执行完 var o=new test(); 后 o.x 就是1 ;
当执行完那个 o.x=3时 o对象的x 值变为3;
test()执行后 x变为1;
x*=10;执行后 x 变为10;改变的是全局对象的x值;
其他的o对象的x值不变;
这个主要是 把test()函数当做构造函数和一般函数的区别!
Denney 说:
楼主写的很好,通过这种简单的例子,让我更改明白的昨天困惑了很久的问题
landuary 说:
this确实很好用啊,用JQuery改变样式的时候深刻体会到
丁小倪 说:
var x = 2;
function test(){
this.x = 1;
}
test();
alert(x); //1
这时其实x发生了改变,与原文总结有出入
其实秘密花园里面总结的不错
1.当在全部范围内使用 this,它将会指向全局对象。
2.函数调用时,这里 this 也会指向全局对象。
3.方法调用 test.foo(); 这个例子中,this 指向 test 对象。
4.调用构造函数 new foo(); 如果函数倾向于和 new 关键词一块使用,则我们称这个函数是构造函数。在函数内部,this 指向新创建的对象。
Pitt Mak 说:
上面这些规则在Nodejs上面好像不怎么准确。
比如:
var x = 1;
function test()
{
console.log(this.x);
}
test(); //不是1,因为nodejs没有全局这个概念,在一个js文件里,最上层的对象不是全局对象,而是对应于这个文件的module对象。所以在nodejs里面上面的规则就不适应了。
akasuna 说:
总之就是一个大原则,谁调用,this 就指向谁
bing 说:
原创:Javascript中的this关键字(详解)
http://hi.baidu.com/flondon/blog/item/1af6b36ffad4cafa81cb4a30.html
qbq 说:
这些例子让人混乱
却没解释为什么
建议想看this用法的人适当参考
Hedgehog 说:
引用Yoya的发言:感觉JS里一些东西真的很混乱,里面的函数即要做爸(类)又要做儿子(类实例的方法)。比如下面的代码就让人感觉无比的混乱。
var x = 2;
function test()
{
this.x = 1;
}
var o = new test();
alert("o.x: " + o.x); //1
o.x = 3;
test();
x*=10;
alert("o.x: " + o.x); //3
alert("x: " + x); //10
其实这里是这样的,第一个x=2是window的属性,第一个x=1,是o对象的属性,
到了o.x = 3;,明显o对象的属性x=3了,然后运行test(),这是,它等于window.test(),所以,这时test()里面的this指向的是window,所以改变的是window的x,所以window.x=1,所以x*=10就是x=1*10
Hedgehog 说:
引用Pitt Mak的发言:上面这些规则在Nodejs上面好像不怎么准确。
比如:
var x = 1;function test()
{
console.log(this.x);
}test(); //不是1,因为nodejs没有全局这个概念,在一个js文件里,最上层的对象不是全局对象,而是对应于这个文件的module对象。所以在nodejs里面上面的规则就不适应了。
毕竟是基于nodejs的,每个js库都有其最上层的对象,这里所指的是底层的,原生的,原始的js吧
zhangyq 说:
引用丁小倪的发言:var x = 2; function test(){ this.x = 1; } test(); alert(x); //1 这时其实x发生了改变,与原文总结有出入 其实秘密花园里面总结的不错 1.当在全部范围内使用 this,它将会指向全局对象。
2.函数调用时,这里 this 也会指向全局对象。3.方法调用 test.foo(); 这个例子中,this 指向 test 对象。
4.调用构造函数 new foo(); 如果函数倾向于和 new 关键词一块使用,则我们称这个函数是构造函数。在函数内部,this 指向新创建的对象。
小倪 说的非常正确。this的确值得大家详细讨论。lz也参与吧,不要仅仅写了博文不参与话题的讨论!期待……
Fenix 说:
引用Andy的发言:记住一条:当function被作为method调用时,this指向调用对象。另外,JavaScript并不是OO的,而是object based的一种语言。之所以你会觉得this用起来混乱,是因为你还没理解JavaScript的诸如全局对象、event handler等一些机制。 所以,阮大侠写的“纯粹函数调用”其实是不“准确”的。
狭义上可以说是“纯粹的函数调用”,如果要更准确,这里所谓的纯粹函数应该是window对象的一个方法,也就是文章里提到的第二点
猪头 说:
其实就是一个作用域的问题。搞清楚了什么时候代表window下的变量,什么时候代表对象的变量,什么时候代表“纯粹函数”中的变量。就可以了。就这么简单。
andiechu 说:
楼主写的这些基础的文章,总是能一下就点明我们这些刚接触语言的初学者!谢谢!!!
李琨 说:
其实总结起来就一句话:
this,正在使用的对象。
firststation 说:
多发一些javascript的博客哈,解释的简洁清析 易懂,顶一个
rubatong 说:
var x = 1;
function test(){
this.x = 0;
}
test();
alert(x);
这个结果是1,x和this.x是两个,x是local的,this.x是window的
FFF永夜 说:
刚好读到 《javascript 语言精粹》,调用一章,就看到博客有摘录了
guxuede 说:
my god!!困扰我多年的偏头疼,我竟然懂了!!!特地留名感谢
fygate 说:
引用rubatong的发言:var x = 1;
function test(){
this.x = 0;
}
test();
alert(x);
这个结果是1,x和this.x是两个,x是local的,this.x是window的
你这个答案绝对是0,怎么可能是1呢,不要误导其它的读者啊,有很多读者是初学者的,当执行test();的时候,this就代表了window对象,这个时候与window.test()是等价的!
wencan 说:
引用Pitt Mak的发言:上面这些规则在Nodejs上面好像不怎么准确。
比如:
var x = 1;function test()
{
console.log(this.x);
}test(); //不是1,因为nodejs没有全局这个概念,在一个js文件里,最上层的对象不是全局对象,而是对应于这个文件的module对象。所以在nodejs里面上面的规则就不适应了。
是咯
我也记得java语言精粹这么讲的,但写个小js文件,用node.js运行下,输出undefined
lucky 说:
很好,写的深入人心啊,理解了
海农 说:
直接执行函数fn()时,就相当于window.fn()。
Eddy 说:
峰哥,这篇文章写的不好啊。感觉还是很模糊。
我的网站里有的内容是转载的你的,http://nodebook.info/book/view?bid=534c8f0519980e913e9be3e6
第二部分编程思想-》 第一章 面向对象编程
Yibin 说:
引用wencan的发言:是咯
我也记得java语言精粹这么讲的,但写个小js文件,用node.js运行下,输出undefined
声明变量x的时候不加var就好
Hiveer 说:
好文!
Denzel.MFFL 说:
在Javascript:the good parts里的第4章有提到这几种用法,不过博主举得例子更通俗易懂!
justme_cq 说:
引用tun的发言:原来 var o = {};是在新建对象。
的确如此,var o=new Object()的另一中写法。
wendy 说:
请问js的call方法和apply方法有什么不同呢?
‘那就是this指的是,调用函数的那个对象。’ 不准确吧
应该是 调用函数那个对象的上下文。
paladintyrion 说:
引用Andy的发言:记住一条:当function被作为method调用时,this指向调用对象。另外,JavaScript并不是OO的,而是object based的一种语言。之所以你会觉得this用起来混乱,是因为你还没理解JavaScript的诸如全局对象、event handler等一些机制。 所以,阮大侠写的“纯粹函数调用”其实是不“准确”的。
没错,阮大侠确实写的不准确
fottchen 说:
引用Yoya的发言:感觉JS里一些东西真的很混乱,里面的函数即要做爸(类)又要做儿子(类实例的方法)。比如下面的代码就让人感觉无比的混乱。
var x = 2;
function test()
{
this.x = 1;
}
var o = new test();
alert("o.x: " + o.x); //1
o.x = 3;
test();
x*=10;
alert("o.x: " + o.x); //3
alert("x: " + x); //10
其实按照阮老师所解释的来看,应该是比较好理解的吧
详解JavaScript中的this
2013/05/07 · Web前端, 开发 · Javascript
分享到:30
来源:foocoder
详解JavaScript中的this
JavaScript中的this总是让人迷惑,应该是js众所周知的坑之一。 个人也觉得js中的this不是一个好的设计,由于this晚绑定的特性,它可以是全局对象,当前对象,或者…有人甚至因为坑大而不用this。
其实如果完全掌握了this的工作原理,自然就不会走进这些坑。来看下以下这些情况中的this分别会指向什么:
1.全局代码中的this
1 |
|
全局范围内的this将会指向全局对象,在浏览器中即使window。
2.作为单纯的函数调用
1 2 3 4 5 |
|
这里this指向了全局对象,即window。在严格模式中,则是undefined。
3.作为对象的方法调用
1 2 3 4 5 6 7 8 |
|
输出 foocoder says hello world。this指向person对象,即当前对象。
4.作为构造函数
1 |
|
函数内部的this指向新创建的对象。
5.内部函数
1 2 3 4 5 6 7 8 9 10 11 |
|
在内部函数中,this没有按预想的绑定到外层函数对象上,而是绑定到了全局对象。这里普遍被认为是JavaScript语言的设计错误,因为没有人想让内部函数中的this指向全局对象。一般的处理方式是将this作为变量保存下来,一般约定为that或者self:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
6.使用call和apply设置this
1 |
|
apply和call类似,只是后面的参数是通过一个数组传入,而不是分开传入。两者的方法定义:
1 2 |
|
两者都是将某个函数绑定到某个具体对象上使用,自然此时的this会被显式的设置为第一个参数。
简单地总结
简单地总结以上几点,可以发现,其实只有第六点是让人疑惑的。
其实就可以总结为以下几点:
1.当函数作为对象的方法调用时,this指向该对象。
2.当函数作为淡出函数调用时,this指向全局对象(严格模式时,为undefined)
3.构造函数中的this指向新创建的对象
4.嵌套函数中的this不会继承上层函数的this,如果需要,可以用一个变量保存上层函数的this。
再总结的简单点,如果在函数中使用了this,只有在该函数直接被某对象调用时,该this才指向该对象。
1 2 3 |
|
更进一步
我们可能经常会写这样的代码:
1 |
|
如果在handler中用了this,this会绑定在obj上么?显然不是,赋值以后,函数是在回调中执行的,this会绑定到$(“#some-div”)元素上。这就需要理解函数的执行环境。本文不打算长篇赘述函数的执行环境,可以参考《javascript高级程序设计》中对执行环境和作用域链的相关介绍。这里要指出的时,理解js函数的执行环境,会更好地理解this。
那我们如何能解决回调函数绑定的问题?ES5中引入了一个新的方法,bind():
1 2 3 4 5 6 |
|
该方法创建一个新函数,称为绑定函数,绑定函数会以创建它时传入bind方法的第一个参数作为this,传入bind方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数.
显然bind方法可以很好地解决上述问题。
1 2 |
|
其实该方法也很容易模拟,我们看下Prototype.js中bind方法的源码:
1 2 3 4 5 6 7 |
|
明白了么?
相信看完全文以后,this不再是坑~