Javascript中this指针的使用实例

Javascript语言中有一个this关键字,后端开发做多了的朋友们习惯称它为this指针。其作用是指向调用当前函数的那个对象。听上去很好理解的一个概念,但是对于后端出身的开发人员来说却很头疼,因为它同Java或C++的this指针效果不一样。JS作为脚本语言,太灵活了。本文就来探讨下,这个this指针,到底指向哪里。

显式函数调用

先看几个显式函数调用的例子

上例中我们定义了全局变量”name”,该变量属于window对象。另外,我们在函数”show()”的内部定义了局部变量,名字也是”name”。同时在函数”show()”的内部打印this指针所指向对象的”name”属性。现在,我们调用”show()”函数,浏览器会打印什么信息呢?

从this的定义中,我们了解到,它是指向调用当前函数的那个对象。现在,”show()”是在全局被调用,也就是window对象在调用它,那么this指针就是指向window对象。以此推论,”this.name”会打印window对象的”name”属性,即全局的那个”name”变量。我们打开浏览器试试,推理没错吧,有种神探夏洛克的赶脚:)

我们再添加下面的代码

现在大家都是推理高手了,一看就知道调用”myObj.show()”函数的对象是”myObj”,所以this指向”myObj”,”this.name”就是”myObj.name”。试下,果然如此吧。

回调函数中的this

上面的例子太简单了,我们来点复杂的。如果函数是在回调时被调用,会是什么情况呢?

还是用上面例子中的”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”变量,一个是全局的,一个是属于”myObj”的,还有一个是”myObj.show()”函数的局部变量。还有个改动就是”myOjb.show()”函数里面还定义了一个内部函数”innerShow()”,并同时调用了这个内部函数。现在我们调用”myOjb.show()”函数,看看会发生什么。奇怪了,为什么全局的”name”被打印出来了?这下百思不得姐了。网上很多文章说这是JS的设计缺陷。我个人觉得吧,硬是要解释,也说得过去,毕竟这个内部函数不是被”myObj”对象调用的,我们并没有看到”myObj.innerShow()”语句。既然没有别的对象在调用它,那只能是window对象了。

如果我们期望在内部函数被调用时,保留其外部函数的上下文呢?那就要用个小技巧:

上面的代码就做了两处改动,都用注释标了出来。首先我们将”myObj.show()”中的this指针赋值给”that”变量。这是一个函数内的局部变量,函数外不可见,但是对于内部函数是可见的。而这个”that”就指向”myObj.show()”的调用者,即”myObj”。然后在内部函数”innerShow()”里,我们用”that.name”即可获得”myObj.name”。

构造函数

接下来讲两个特殊的函数调用情况,首先是构造函数。在JS对象继承和原型链一文中,我们介绍过,构造函数是用”new”操作符来调用的。当”new”被使用时,其实JS做了下面几件事情:

  1. 创建一个新对象,类型是object
  2. 将这个新对象的”__proto__”属性指向构造函数的原型,即设置原型链
  3. 用这个新对象来调用构造函数
  4. 返回该新对象

从上面的第三和第四点中可以了解到,构造函数的调用者即”new”操作返回的对象,那构造函数里用到的this,也将指向这个返回的对象。看看下面的例子:

“new Show()”操作返回了对象”newObj”,那构造函数中的”this.name”就是指”newObj.name”。打印出来看看,的确如此吧。

使用call/apply/bind来调用函数

“call”, “apply”和”bind”方法都是”Function”原型上的成员函数。由于所有的函数对象都继承于”Function”的原型(感兴趣的朋友们可以打印出上例中”Show.__proto__”看看),因此所有的函数对象都可以调用”call”, “apply”和”bind”方法。这些方法有什么用呢?

先来看下”call”和”apply”,这两个很相似。”call”方法的用法如下:

使用”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”,代码如下:

运行下看结果,是不是同”call”方法完全一样?”show”函数中this指针指向的是”apply”方法的第一个参数。

最后聊下”bind”方法,这是在IE9+, Firefox4+, Safari5.1+, 以及Chrome7+中才支持的,属于ECMAScript5标准。它可以将已有的函数对象复制一份出来,并同时绑定这个新函数对象的调用者。还是看例子吧:

依然是前面的”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″函数,除了调用者被绑定为”myObj”对象外,其第一个参数也被绑定为整数1。调用”myShow2(2)”就等同于调用”myObj.show(1, 2)”。

“call”, “apply”和”bind”方法的官方解释,就是用来改变函数的上下文执行环境”Execution Context”。简而言之,就是改变函数中”this”指针的指向。”call”和”apply”会立即执行函数,而”bind”可以保存起来稍后执行。

说到这里,大家对this指针的指向应该已经很清楚了吧。其实从某种角度说,在后端开发语言中,this指针也是指向调用当前函数的对象的。只不过,在Java这样的语言里,类中的成员函数一定是被该类所实例化的对象来调用的,你没法把一个函数赋值成另一个对象的属性。而Javascript可以,在一个对象中定义的函数可以由任何一个其他对象来调用,所以this指针的指向只有在运行时才能确定。

转载请注明出处: 思诚之道

发表评论

电子邮件地址不会被公开。 必填项已用*标注