Javascript中this指针的使用实例
Javascript语言中有一个this
关键字,后端开发做多了的朋友们习惯称它为”this指针”。其作用是指向调用当前函数的那个对象。听上去很好理解的一个概念,但是对于后端出身的开发人员来说却很头疼,因为它同Java或C++的this指针效果不一样。JS作为脚本语言,太灵活了。本文就来探讨下,这个”this指针”,到底指向哪里。
显式函数调用
先看几个显式函数调用的例子
name = "name of window";
function show() {
var name = "name in function show()";
alert(this.name);
}
show();
上例中我们定义了全局变量name
,该变量属于window
对象。另外,我们在函数show()
的内部定义了局部变量,名字也是name
。同时在函数show()
的内部打印”this指针”所指向对象的name
属性。现在,我们调用show()
函数,浏览器会打印什么信息呢?
从this
的定义中,我们了解到,它是指向调用当前函数的那个对象。现在,show()
是在全局被调用,也就是window
对象在调用它,那么”this指针”就是指向window
对象。以此推论,this.name
会打印window
对象的name
属性,即全局的那个name
变量。我们打开浏览器试试,推理没错吧,有种神探夏洛克的赶脚:-)
我们再添加下面的代码
var myObj = {
name: "name of myObj",
show: function() {
var name = "name in myObj's function show()";
alert(this.name);
},
};
myObj.show();
现在大家都是推理高手了,一看就知道调用myObj.show()
函数的对象是myObj
,所以this
指向myObj
,this.name
就是myObj.name
。试下,果然如此吧。
回调函数中的this
上面的例子太简单了,我们来点复杂的。如果函数是在回调时被调用,会是什么情况呢?
<div id="sample">Click Me</div>
<script type="text/javascript">
var myObj = {
name: "name of myObj",
show: function() {
var name = "name in myObj's function show()";
alert(this.name);
},
};
document.getElementById('sample').onclick = myObj.show;
</script>
还是用上面例子中的myObj
,不同的是,我们不直接调用它的show()
函数,而且将其绑定到div的点击事件中去。当我们点击页面上”Click Me”字样时,这个函数就会被调用。那这个时候,this
会不会还是指向myObj
呢?我们试下,奇怪了,弹出了”undefined”。为什么呢?聪明的朋友们马上反应过来,现在调用show()
方法的对象主体已经不再是myObj
,而是id为sample
的这个div的DOM对象。依此类推,this
指针应该指向这个DOM对象喽?我们将函数中打印的内容改为alert(this.id)
试试,果然,对话框弹出了”sample”,看来this
的确指向了id为sample
的DOM对象。这也符合this
的定义,指向调用当前函数的对象。大家可以另外再试下Ajax回调中this
是指向哪里,再想想为什么。
内部函数的this
再来个更复杂的情形,我们知道函数内部还可以定义内部函数,那么在函数里调用内部函数时,this
会是什么情况呢?还是利用上面myObj
的例子:
name = "name of window";
var myObj = {
name: "name of myObj",
show: function() {
var name = "name in myObj's function show()";
function innerShow() {
alert(this.name);
}
innerShow();
},
};
myObj.show();
这个例子会有点让人混淆,我们有三个name
变量,一个是全局的,一个是属于myObj
的,还有一个是myObj.show()
函数的局部变量。还有个改动就是myOjb.show()
函数里面还定义了一个内部函数innerShow()
,并同时调用了这个内部函数。现在我们调用myOjb.show()
函数,看看会发生什么。奇怪了,为什么全局的name
被打印出来了?这下百思不得姐了。网上很多文章说这是JS的设计缺陷。我个人觉得吧,硬是要解释,也说得过去,毕竟这个内部函数不是被myObj
对象调用的,我们并没有看到myObj.innerShow()
语句。既然没有别的对象在调用它,那只能是window
对象了。
如果我们期望在内部函数被调用时,保留其外部函数的上下文呢?那就要用个小技巧:
name = "name of window";
var myObj = {
name: "name of myObj",
show: function() {
var name = "name in myObj's function show()";
var that = this; // Reserve "this" of myObj.show()
function innerShow() {
alert(that.name); // Replace "this" by "that"
}
innerShow();
},
};
myObj.show();
上面的代码就做了两处改动,都用注释标了出来。首先我们将myObj.show()
中的this
指针赋值给that
变量。这是一个函数内的局部变量,函数外不可见,但是对于内部函数是可见的。而这个that
就指向myObj.show()
的调用者,即myObj
。然后在内部函数innerShow()
里,我们用that.name
即可获得myObj.name
。
构造函数
接下来讲两个特殊的函数调用情况,首先是构造函数。在JS对象继承和原型链一文中,我们介绍过,构造函数是用new
操作符来调用的。当new
被使用时,其实JS做了下面几件事情:
- 创建一个新对象,类型是
object
- 将这个新对象的
__proto__
属性指向构造函数的原型,即设置原型链 - 用这个新对象来调用构造函数
- 返回该新对象
从上面的第三和第四点中可以了解到,构造函数的调用者即new
操作返回的对象,那构造函数里用到的this
,也将指向这个返回的对象。看看下面的例子:
function Show() {
this.name = "name in constructor Show()";
}
newObj = new Show();
alert(newObj.name);
new Show()
操作返回了对象newObj
,那构造函数中的this.name
就是指newObj.name
。打印出来看看,的确如此吧。
使用call/apply/bind来调用函数
call
, apply
和bind
方法都是Function
原型上的成员函数。由于所有的函数对象都继承于Function
的原型(感兴趣的朋友们可以打印出上例中Show.__proto__
看看),因此所有的函数对象都可以调用call
, apply
和bind
方法。这些方法有什么用呢?
先来看下call
和apply
,这两个很相似。call
方法的用法如下:
name = "name of window";
var myObj = {
name: "name of myObj",
}
function show(x, y) {
alert(this.name);
return x + y
}
show.call(window, 1, 2);
show.call(myObj, 1, 2);
使用show.call()
方法时,第一个参数就是指定调用show(x, y)
函数的对象,第二个参数将是show(x, y)
函数的第一个参数x,第三个参数则是show(x, y)
函数的第二个参数y。换句话说,show.call(window, 1, 2)
等同于window.show(1, 2)
,而show.call(myObj, 1, 2)
则等同于myObj.show(1, 2)
。不管在对象window
或myObj
上有没有定义过show(x, y)
函数,都可以成功调用。结论,函数上的call
方法,可以用来指定该函数的调用者。这样,函数里的this
,即指向调用者指针,就会指向call
方法的第一个参数。我们运行下上面的例子看看,是不是这样?
然后说下apply
方法,它同call
方法的功能完全一样,只不过它把函数的参数放在一个数组中,而不像call
方法,将函数的参数从第二个参数开始一一列出来,所以在参数比较多时apply
方法看上去更简洁些。我们将上面例子中的call
改为apply
,代码如下:
numArray = [1, 2];
show.apply(window, numArray);
show.apply(myObj, numArray);
运行下看结果,是不是同call
方法完全一样?show
函数中this
指针指向的是apply
方法的第一个参数。
最后聊下bind
方法,这是在IE9+, Firefox4+, Safari5.1+, 以及Chrome7+中才支持的,属于ECMAScript5标准。它可以将已有的函数对象复制一份出来,并同时绑定这个新函数对象的调用者。还是看例子吧:
name = "name of window";
var myObj = {
name: "name of myObj",
}
function show(x, y) {
alert(this.name);
return x + y
}
myShow = show.bind(myObj);
show(1, 2);
myShow(1, 2);
依然是前面的show(x, y)
函数和myObj
对象。当调用show(1, 2)
时,我们一开始就聊过,这时this
指向window
对象。然而,如果我们用show.bind(myObj)
来创建一个新的函数对象myShow
时,myShow
的功能将同show()
函数一模一样,唯一的区别,就是它的调用者,将被强制绑定为myObj
。所以,即使你在全局调用myShow(1, 2)
,它的调用者依然是myObj
,而不是window
对象。你可以试下。
bind()
方法除了可以绑定调用者之外,还可以绑定参数。比如上面show(x, y)
函数有两个参数,我们可以绑定其中第一个参数(也可以两个都绑定)。方法如下:
myShow2 = show.bind(myObj, 1);
myShow2(2);
此时,对于myShow2
函数,除了调用者被绑定为myObj
对象外,其第一个参数也被绑定为整数1
。调用myShow2(2)
就等同于调用myObj.show(1, 2)
。
call
, apply
和bind
方法的官方解释,就是用来改变函数的上下文执行环境”Execution Context”。简而言之,就是改变函数中this
指针的指向。call
和apply
会立即执行函数,而bind
可以保存起来稍后执行。
说到这里,大家对this
指针的指向应该已经很清楚了吧。其实从某种角度说,在后端开发语言中,this
指针也是指向调用当前函数的对象的。只不过,在Java这样的语言里,类中的成员函数一定是被该类所实例化的对象来调用的,你没法把一个函数赋值成另一个对象的属性。而Javascript可以,在一个对象中定义的函数可以由任何一个其他对象来调用,所以this
指针的指向只有在运行时才能确定。