小提示

阅读本文,您需要了解JS的基本常识。您将花费40分钟完成本文的阅读。

JS中的this关键字让很多新老JS开发人员都感到困惑。这篇文章将对this关键字进行完整地阐述。读完本文以后,您的困惑将全部消除。您将学会如何在各种不同的情形正确运用this。

我们和在英语、法语这样的自然语言中使用名词一样地使用this。比如,“John飞快地跑着,因为他想追上火车”。请注意这句话中的代指John的代名词“他”。我们原本也可以这样表达,“John飞快地跑着,因为John想追上火车”。按照正常的语言习惯,我们并不按第二种方式表达。如果我们真按第二种方式说话,我们的家人和基友一定会把我们当成怪胎。说不定不止家人,甚至连我们的酒肉朋友和同事都会远离我们。类似地,在JS中,我们把this关键字当成一种快捷方式,或者说是引用(referent)。this关键字指向的是当前上下文(context,下文中将会对此作专门的解释)的主体(subject),或者当前正在被执行的代码块的主体。

考虑以下代码,

  1. var person = {
  2. firstName: "Penelope",
  3. lastName: "Barrymore",
  4. fullName: function () {
  5. // 正如我们在文中提到的使用“他”作为代名词一样,我们在这里使用this
  6.  
  7. console.log(this.firstName + " " + this.lastName);
  8. //我们其实也可以这样写:
  9. console.log(person.firstName + " " + person.lastName);
  10. }
  11. }

如果我们使用person.firstName和person.lastName,某些情况下代码会变得模棱两可。例如,全局变量中有一个跟person同名的变量名。这种情况下,如果我们想要读取person.firstName的值,系统将有可能从全局变量的person变量中读取firstName属性(property)。这样一来,我们调试代码的时候很难发现错误。所以this并不只起到美化代码的作用,同时也是为了保证程序的准确性。这种做法实际上和前面讲到的“他”的用法一样,使得我们的代码更加清晰。“他”所引用的正是句首的“John”。

正如代名词“他”被用来代指句中的先行词(先行词就是代名词所指示的名词),this关键字以同样的方式来引用当前方法(function,也可以称为函数)所绑定(bound)的对象。this不只引用对象,同时包含了对象的值。跟先行词一样,this也可以被理解成在上下文中用来引用当前对象(又叫作“先行词对象”)的快捷方式(或者来适度减少歧义的替代品)。我们迟些会专门讲解“上下文”。

this关键字基本理论

首先我们得知道,对象(Object)有属性集(properties),所有的方法(function)也有属性集。运行到某个方法的时候就有了一个this属性—一个存储了调用该方法(准确地说是使用了this关键字的方法)的对象的值的变量。

this关键字始终指向一个对象并持有这个对象的值,尽管它可以出现在全局范围(global scope)方法(函数)以外的地方,但它通常出现在方法体中。值得注意的是,如果我们使用严格模式(strict mode),并在全局方法(global functions)或者没有绑定到任何对象的匿名方法中使用this关键字时,this将会指向undefined。

this被用在方法体中,比如方法A,它将指向调用方法A的对象的值。并不是任何情况我们都能找到调用方法A的对象名,这时候就用this来访问调用方法A的对象所拥有的方法和属性。this确实只是一个用来引用先行词—调用方法的对象—的快捷方式。

我们来仔细体会下面这段使用this的代码。

  1. var person = {
  2. firstName: "Penelope",
  3. lastName: "Barrymore",
  4. //this用在showFullName方法中,而showFullName定义在person对象中,由于调用showFullName的是person这个对象,所以this拥有person的值
  5.  
  6. showFullName: function() {
  7. console.log(this.firstName + " " + this.lastName);
  8. }
  9. }
  10. person.showFullName(); // 结果:Penelope Barrymore

再考虑下面这段使用了this的jQuery示例。

  1. //这是一段很简单的jQuery代码
  2.  
  3. $("button").click(function(event) {
  4. // $(this) 会指向$("button")对象
  5. // 因为$("button")对象调用click方法
  6. console.log($(this).prop("name"));
  7. });

我想详细地说一下上面这个jQuery示例:$(this)的使用,这是this的jQuery版本,它用于匿名方法中,这个匿名方法在button的单击事件里执行。这里之所以说$(this)被绑定到button对象,是因为jQuery库把$(this)绑定到调用click方法的对象上。因此,尽管$(this)被定义在一个自身无法访问“自身”变量的匿名方法里,$(this)仍会指向button对象。

请注意,button是一个HTML页面的DOM元素,它同时是一个对象:在上面的例子中,因为我们把它包装在了jQuery的$()方法里,所以它是一个jQuery对象。

this关键字的核心

下面这条规则可以帮助你彻底搞懂this关键字:如果一个方法内部使用了this关键字,仅当对象调用该方法时this关键字才会被赋值。我们估且把使用了this关键字的方法称为“this方法”。

尽管看上去this引用了它在代码中所存在于的对向,事实上在方法被调用之前它并没有被赋值,而赋给它的值又严格地依赖于实际调用“this方法”的对象。this通常会被赋予调用对象的值,下面有一些特殊情况。

全局范围里的this

在全局域中,代码在浏览器里执行,所有变量和方法都属于window对象。因而当我们在全局域中使用this关键字的时候,它会被指向(并拥有)全局变量window对象。如前所述,严格模式除外。window对象是JS一个程序或一张网页的主容器。

因而:

  1. var firstName = "Peter",
  2. lastName = "Ally";
  3.  
  4. function showFullName() {
  5. //在这个方法中,this将指向window对象。因为showFullName()出现在全局域里。
  6.  
  7. console.log(this.firstName + " " + this.lastName);
  8. }
  9.  
  10. var person = {
  11. firstName: "Penelope",
  12. lastName: "Barrymore",
  13. showFullName: function() {
  14. //下面这行代码,this指向person对象,因为showFullName方法会被person对象调用。
  15. console.log(this.firstName + " " + this.lastName);
  16. }
  17. }
  18.  
  19. showFullName(); // Peter Ally
  20.  
  21. //window对象包含了所有的全局变量和方法,因而会有以下输出
  22. window.showFullName(); // Peter Ally
  23.  
  24. //使用了this关键字的showFullName方法定义在person对象里,this关键字指向person对象,因以会有以下输出
  25. person.showFullName(); // Penelope Barrymore

对付this有绝招

当方法内使用了this关键字时,这几种情况最容易引起误解:方法被借用;把方法赋值给某个变量;方法被用作回调函数(callback),被作为参数传递;this所在的方法是个闭包(该方法是一个内部方法)。针对这几种情况,我们将逐一攻破。在此之前,我们先简单介绍一下“上下文”(context)。

JS当中的上下文跟这句话中的主语(subject)类似:“John是赢家,他还了钱”。这句话的主语是John。我们也可以说这句话的上下文是John,因为我们在这句话中关注的是John,即使这里有一个“他”字来代指John这个先行词。正如我们可以使用分号来切换句子的主语一样,通过使用不同的对象来对方法进行调用,当前的上下文对象同样可以被切换。

类似地,以下JS代码:

  1. var person = {
  2. firstName: "Penelope",
  3. lastName: "Barrymore",
  4. showFullName: function() {
  5. // 上下文
  6. console.log(this.firstName + " " + this.lastName);
  7. }
  8. }
  9.  
  10. //使用person对象调用showFullName的时候,上下文是person对象
  11. //showFullName内部的this指向person对象
  12. person.showFullName(); // Penelope Barrymore
  13. //如果我们用不同的对象来调用showFullName
  14. var anotherPerson = {
  15. firstName: "Rohit",
  16. lastName: "Khan"
  17. };
  18.  
  19. //我们可以使用apply方法来显式设置this的值—迟些我们会讲到apply方法
  20. //this会指向任何一个调用了this方法的对象,因此会有以下输出结果
  21. person.showFullName.apply(anotherPerson); // Rohit Khan
  22. //所以现在的上下文是anotherPerson,因为anotherPerson通过借助使用apply方法间接调用了person的showFullName方法

现在我们开始正式讨论应付this关键字的绝招,例子里包含了this所引发的错误以及解决方案。

1.当this被用作回调函数传入其它方法

当我们把一个使用了this关键字的方法当成参数作为回函数的时候,麻烦就来了。例如:

  1. //以下是一个简单的对象,我们定义了一个clickHandler方法。我们想让这个方法在页面上某个button被单击时执行。
  2. var user = {
  3. data: [{
  4. name: "T. Woods",
  5. age: 37
  6. },
  7. {
  8. name: "P. Mickelson",
  9. age: 43
  10. }],
  11. clickHandler: function(event) {
  12. var randomNum = ((Math.random() * 2 | 0) + 1) - 1; // 随机返回0或1
  13. //下面这行代码会从数组data里随机打印姓名和年龄
  14. console.log(this.data[randomNum].name + " " + this.data[randomNum].age);
  15. }
  16. }
  17.  
  18. //button对象被jQuery的$方法包装,现在变成一个jQuery对象
  19. //所以输出结果是undefined,因为button对象没有data这个属性
  20. $("button").click(user.clickHandler); // 无法读取未定义的属性

上面的代码中,我们把user.clickHandler当成回调函数传入$(“button”)对象的click事件,user.clickHandler中的this将不再指向user对象转。谁调用了这个包含this的方法this就会指向谁。真正调用user.clickHandler的对象是button对象—user.clickHandler会在button对象的单击方法里执行。

注意,尽管我们使用user.clickHandler来调用clickHander方法(我们也只能这么做,因为clickHandler是定义在user对象上的),clickHandler方法本身会被现在被this所指向的上下文对象所调用。所以this现在指向的是$(“button”)对象。

当上下文改变时—当我们在其它对象而非原对象上执行某个方法的时候,显然this关键字不再指向定义了this关键字的原对象。

解决方案:

由于我们真的很想让this.data指向user对象的data属性,我们可以使用Bind/ Apply/ Call等方法来强制改变this所指向的对象。本系列的其它篇目将专门对Bind/ Apply/ Call进行讲解,文中介绍了如何在不同的情况强制改变this的值的方法。与其在本文大篇幅讨论,我强烈建议大家直接去读另外的篇目(译者注:晚些时候放出这里所说的“其它篇目”)。

为了解决前面代码中的问题,我们可以使用bind方法。

针对下面这行代码:

  1. $ ("button").click (user.clickHandler);

我们可以用bind方法把clickHandler绑定的user对象上:

  1. $("button").click (user.clickHandler.bind (user)); // P. Mickelson 43

2.闭包中的this

在内部方法中,或者说闭包中使用this,是另一个很容易被误解的例子。我们必须注意的是,内部方法不能直接通过使用this关键字来访问外部方法的this变量,因为this变量 只能被特定的方法本身使用。例如:

  1. var user = {
  2. tournament: "The Masters",
  3. data: [{
  4. name: "T. Woods",
  5. age: 37
  6. },
  7. {
  8. name: "P. Mickelson",
  9. age: 43
  10. }],
  11.  
  12. clickHandler: function() {
  13. //在里用this.data没有太大问题,因为this指向的是user对象,data是user的一个属性
  14. this.data.forEach(function(person) {
  15. //但是在这个匿名方法(作为参数被传进forEach方法的这个方法)里,this不再指向user对象
  16. //内部方法无法访问外部方法的this
  17. console.log("What is This referring to? " + this); //输出结果为:[object Window]
  18. console.log(person.name + " is playing at " + this.tournament);
  19. // T. Woods is playing at undefined
  20. // P. Mickelson is playing at undefined
  21. })
  22. }
  23.  
  24. }
  25.  
  26. user.clickHandler(); // What is "this" referring to? [object Window]

因为匿名方法中的this不能访问外部方法的this,所以在非严格模式下,this指向了全局的window对象

解决方案:

在进入forEach方法之前,额外使用一个变量来引用this。

  1. var user = {
  2. tournament: "The Masters",
  3. data: [{
  4. name: "T. Woods",
  5. age: 37
  6. },
  7. {
  8. name: "P. Mickelson",
  9. age: 43
  10. }],
  11.  
  12. clickHandler: function(event) {
  13. //为了捕获this指向user对象时的值,我们把它赋值给另外一个变量theUserObj,后面我们可以使用theUserObj
  14. var theUserObj = this;
  15. this.data.forEach(function(person) {
  16. //现在我们不用this.tournament了,我们用theUserObj.tournament
  17. console.log(person.name + " is playing at " + theUserObj.tournament);
  18. })
  19. }
  20.  
  21. }
  22.  
  23. user.clickHandler();
  24. // T. Woods is playing at The Masters
  25. // P. Mickelson is playing at The Masters

正如下面的代码,很多JS开发人员喜欢使用变量that来设置this的值。但我个人不太喜欢用that这个名字,我喜欢使用让人一眼就能看懂this到底指向谁的那种名字,所以上面的代码中我使用了theUserObj = this。

  1. // 这句代码对大多数JS开发人员来说再常见不过了
  2. var that = this;

3.方法被赋值给某个变量

this关键字有时候很调皮,如果我们把一个使用了this关键字的方法赋值给一个变量,我们来看会有什么有趣的事发生:

  1. // data变量是一个全局变量
  2. var data = [{
  3. name: "Samantha",
  4. age: 12
  5. },
  6. {
  7. name: "Alexis",
  8. age: 14
  9. }];
  10.  
  11. var user = {
  12. // 而这里的data是user的一个属性
  13. data: [{
  14. name: "T. Woods",
  15. age: 37
  16. },
  17. {
  18. name: "P. Mickelson",
  19. age: 43
  20. }],
  21. showData: function(event) {
  22. var randomNum = ((Math.random() * 2 | 0) + 1) - 1; // 随机生成1或0
  23. //这句话会从数组data里随机显示人名和岁数
  24. console.log(this.data[randomNum].name + " " + this.data[randomNum].age);
  25. }
  26.  
  27. }
  28.  
  29. // 把user.showData方法赋值给变量 showUserData
  30. var showUserData = user.showData;
  31.  
  32. //执行showUserData方法,结果将 来自全局的data数组而非user对象的data属性
  33. showUserData(); // Samantha 12 (来自全局变量data)
  34. //解决方案:通过使用bind方法来显式设置this的值
  35. //把showData方法绑定到user对象上
  36. var showUserData = user.showData.bind(user);
  37.  
  38. //现在结果将来自user对象,因为this关键字已经被强制绑定到user对象上了
  39. showUserData(); // P. Mickelson 43

4.借用方法带来的问题

JS开发中,借用方法(borrowing methods)很常见。关于借用方法,请参考本系列的其它篇目。

我们来看下面的代码:

  1. //下面代码中有两个对象。其中一个定义了avg方法,另一个不包含avg的定义。我们用另一个对象来借用前一对象的avg方法。
  2. var gameController = {
  3. scores: [20, 34, 55, 46, 77],
  4. avgScore: null,
  5. players: [{
  6. name: "Tommy",
  7. playerID: 987,
  8. age: 23
  9. },
  10. {
  11. name: "Pau",
  12. playerID: 87,
  13. age: 33
  14. }]
  15. }
  16.  
  17. var appController = {
  18. scores: [900, 845, 809, 950],
  19. avgScore: null,
  20. avg: function() {
  21.  
  22. var sumOfScores = this.scores.reduce(function(prev, cur, index, array) {
  23. return prev + cur;
  24. });
  25.  
  26. this.avgScore = sumOfScores / this.scores.length;
  27. }
  28. }
  29.  
  30. //如果执行下面的代码,gameController.avgScore属性的实际取值将由appController的scores而来
  31. //不要执行下面的代码,我们只是为了对这种情况进行说明。实际上我们想让appController.avgScore仍然为null。
  32. gameController.avgScore = appController.avg();

avg方法的this关键字指向的是gameController对象,如果使用appController调用该方法,this将会指向appController(但事实上这并不是我们期望的结果,因为我们只想借用方法的实现逻辑而非具体的数据来源)。

解决方案:

为了保证gameController只借用appController的avg方法的逻辑,我们使用apply方法:

  1. // 我们要使用apply方法,注意这里传入appController.avg方法的第二个参数
  2. appController.avg.apply(gameController, gameController.scores);
  3.  
  4. //尽管avg方法是借来的,但是现在avgScore属性已经被成功地应用到gameController上了。
  5. console.log(gameController.avgScore); // 46.4
  6. //appController.avgScore仍然是null,只有gameController的avgScore被更新了
  7. console.log(appController.avgScore); // null

gameController只借用了appController的avg方法,这时this将指向gameController,因为我们把gameController作为apply方法的第一个参数进行传递。apply方法的第一个参数将会显式设置this的取值。

结语

希望您在文中有所收获。现在你可以使用文中介绍的绝招(bind方法,apply方法,call方法,以及把this赋值给 一个变量)来对付跟this相关的任何问题。

正如已经了解到的,this在上下文改变、被作为回调函数使用、被不同的对象调用、或者方法被借用的情况下,this将一直指向调用当前方法的对象。

顺祝大家万事如意,写代码写得开心!晚安!

原文链接:http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/

翻译不妥之处请大家批评指正,转载请注明出处。

精通JavaScript的this关键字的更多相关文章

  1. <精通JavaScript>---阅读笔记01

    下面是阅读精通JavaScript书做的相关笔记. JS中的函数重载 函数重载必须依赖两件事情:判断传入参数数量的能力和判断传入参数类型的能力,在js中每个函数都带有一个仅在这个函数范围内作用的变量, ...

  2. 浅谈JavaScript的New关键字

    原型和闭包算是JavaScript中最常见,最难以理解,最容易被当做问题的两个部分,当然还有它们的延伸,如作用域链,继承等等吧,我最近也是各种看,各种翻,记录点自己的心得,写写总会让自己的理解更深一些 ...

  3. [No000069]Javascript中this关键字详解

    Quiz 请看下面的代码,最后alert出来的是什么呢?(chrome下按F12,选择Console直接复制粘贴运行) var name = "Bob"; var nameObj ...

  4. 深入解析Javascript中this关键字的使用

    深入解析Javascript中面向对象编程中的this关键字 在Javascript中this关键字代表函数运行时,自动生成的一个内部对象,只能在函数内部使用.比如: function TestFun ...

  5. 谈谈Javascript的this关键字(this is not this)

    前言: 看文章标题你就知道,这篇文章我只讲一个简单的Javascript的this关键字,说它简单——它又不简单,因为曾几何时我也对this关键字有些困惑,它也确实会让不少程序员感到不解——它像是一个 ...

  6. 正确理解javascript的this关键字

    javascript有this关键字,它和javascript的执行上下文有着密切的关系,就是说this具体指代什么要根据它的上下文来判断. 一.this和对象的关系    var Person={ ...

  7. 深入理解Javascript之this关键字

    深入理解Javascript之this关键字 作者: Laruence(   ) 本文地址: http://www.laruence.com/2009/09/08/1076.html 转载请注明出处 ...

  8. JavaScript中this关键字的使用比较

    JavaScript中this关键字的使用比较 this关键字在JavaScript中,用的不能说比较多,而是非常多.那么熟悉this关键字的各种用法则显得非常关键. this有时候就是我们经常说的上 ...

  9. JavaScript控制流及关键字与C语言之比较

    学习JavaScript控制流及关键字概念前,对有过C语言学习经验的同学来说,那么关键字,控制语句概念并不陌生.我们先来看看C语言吧: C语言的32个关键字和9种控制语句 9种控制语句: if.if- ...

随机推荐

  1. 【Highcharts】 绘制饼图和漏斗图

    1.outModel类设计 设计outModel类首先研究下Highcharts中series的data数据格式,发现饼图和漏斗图都可以使用这样格式的数据 series: [{ name: 'Uniq ...

  2. Codeforces 676C Vasya and String(尺取法)

    题目大概说给一个由a和b组成的字符串,最多能改变其中的k个字符,问通过改变能得到的最长连续且相同的字符串是多长. 用尺取法,改变成a和改变成b分别做一次:双指针i和j,j不停++,然后如果遇到需要改变 ...

  3. Java类加载

    类的生命周期是: 在一个类编译完成之后,下一步就需要开始使用类,如果要使用一个类,肯定离不开JVM.在程序执行中JVM通过装载,链接,初始化这3个步骤完成. 类的装载是通过类加载器完成的,加载器将.c ...

  4. Air Raid[HDU1151]

    Air RaidTime Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submis ...

  5. 流式大数据处理的三种框架:Storm,Spark和Samza

    许多分布式计算系统都可以实时或接近实时地处理大数据流.本文将对三种Apache框架分别进行简单介绍,然后尝试快速.高度概述其异同. Apache Storm 在Storm中,先要设计一个用于实时计算的 ...

  6. [转]maven安装以及eclipse配置maven

    转自:http://jingyan.baidu.com/article/295430f136e8e00c7e0050b9.html 方法/步骤 下载maven的bin,在apache官方网站可以下载. ...

  7. 内网配置DNS服务器,无域名,只有主机名

    Hadoop集群中,使用DNS而不是hosts来访问服务器. 1. 安装bind软件 用root用户运行: yum -y install bind* 2. 配置named.conf文件 vi /etc ...

  8. Android -----listView的属性大全

    http://www.cnblogs.com/zhengbeibei/archive/2013/03/29/2988814.html 01     <?xml version="1.0 ...

  9. topcoder SRM 617 DIV2 SlimeXSlimonadeTycoon

    此题需要注意的两个地方是 (1)在某天生产出来的Slimonades,必须在stale_limit天内必须卖完,否则超过stale_limit内抛弃(东西都有保质期) (2)每天生产出来的Slimon ...

  10. IOS UI segmentedControl UISegmentedControl 常见属性和用法

    UISegmentedControl中一些常见的属性和用法 //设置以图案作为分段的显示,仅需要图案的轮廓,这样颜色为分段的背景颜色 //    NSArray *items = @[[UIImage ...