英文原文

学习前提:需要懂一些JS知识 学习耗时:约40分钟

前言

无论是JavaScript新手还是老手,JavaScript中的this关键词可能都会令你困惑。本文旨在透彻地阐述this。读完本文,就再也不用怕JavaScript中的this了。你将会知道在各种情况下怎么使用this,即使是最晦涩的用法也将不再是问题。

this的用法与自然语言英语法语中的代词差不多。我们会这么说:“约翰跑的很快,因为他正在赶火车。”注意代词“他”的用法。我们也可以这样说:“约翰跑的很快,因为约翰正在赶火车。”我们当然不会以这种方式复用“约翰”,因为这样子我们的家人朋友和同事都会对我们避而远之,真的会的。好吧,可能你的家人不会,但有些酒肉朋友或者不能共患难的同事则会抛弃你。与在自然语言中优雅地使用代词的方式类似,在JavaScript中我们将this这个关键词当成快捷方式,当成指示词。它指代的是一个物体–上下文环境中的主语(上下文指的是执行代码的主语)。看看下面这个例子:

var person = {firstName: "Penelope",lastName: "Barrymore",fullName: function () {//注意this的使用方式与上文使用“他”这个代词的方式一样:console.log(this.firstName + " " + this.lastName);//我们也可以这样子写:console.log(person.firstName + " " + person.lastName);}
}

如果我们像上方示例代码那样子使用person.firstNameperson.lastName,代码就会变得指代不明。假设还有一个叫person的全局变量(不管你知不知道),那么person.firstName就会尝试访问全局变量personfirstName属性,这样子出错时就很难调试了。所以this关键词的使用不单单是为了简洁的美感(比如将其作为指示词),也是为了准确性。正如代词“他”让句子含义更加清晰一样,使用this可以让代码的含义更加明确。上方范例句子中,代词让我们知道它指代的是句子开头提到的那个“约翰”,而this关键词的作用也于此类似。

代词“他”用来指代先行词(先行词就是其指代的名词),相似地this关键词也用来指代使用它的函数(function)绑定的对象(object)。this关键词不单单指代对象,还会包含该对象的值。与代词一样,this可以当成快捷方式(或者指代明确的替身),指代上下文环境中的对象(或者你可以叫它“先行对象”)。下文我们会学习更多关于上下文环境的内容。

JavaScript this关键词基础知识

首先你要知道JavaScript中所有的函数都有属性,就如对象有属性一样。函数执行时会获取this属性的值,此时this就是一个变量,储存着调用该函数的对象的值。

this这个引用总是指代对象并储存着它的值(只能指代一个对象),一般都在函数或者对象方法里使用,但是也能用在函数外的全局作用域里。需要注意的是,如果在函数里使用严格模式,全局函数里this的值就是undefined。而在匿名函数里则不会绑定任何对象。

假设在函数A里使用this,它就储存着调用函数A的对象的值。要获取调用函数A的对象的属性和方法,就需要用到this,特别是当我们不知道改对象的名称或者没有名称可以指代该对象。所以,需要用this作为一个快捷方式来指代“先行对象”,也就是调用函数的对象。

思考一下下面这段代码,它展示了如何在JavaScript中使用this

var person = {firstName   :"Penelope",lastName    :"Barrymore",//因为下方的showFullName方法使用了“this”关键词而且该方法是在person对象里定义的,//那么“this”就会储存着person对象的值,因为该对象会调用showFullName()方法showFullName:function () {console.log (this.firstName + " " + this.lastName);}
​}
​person.showFullName (); // Penelope Barrymore

看看下面很基础的jQuery范例代码,里面也用了this

    //这是一段很常见的jQuery代码$ ("button").click (function (event) {//$(this)会储存按钮对象($("button"))的值//因为click()方法是由该按钮对象调用的console.log ($ (this).prop ("name"));
});

我解释一下上面的jQuery范例。JavaScript this关键词在jQuery里的语法形式是$(this),它被用在一个匿名函数里面,而该函数则在按钮对象的click()方法里执行。$(this)会被绑定到按钮对象,是因为jQuery库将$(this)绑定到了调用click方法的对象中。所以尽管$(this)在匿名函数里定义且该函数本身无法访问外部函数的this变量,$(this)还是储存着jQuery按钮对象$(“button”)的值。

需要注意的是,该按钮不但是HTML页面的DOM元素,而且是一个对象。因为我们将它封装在 jQuery $()函数里,在这种情况下它就是一个jQuery对象。

更新:下文的“最大的误区”部分是在本文发布几天后新增的。

JavaScript this 关键词使用最大的误区

只有当定义this的函数被对象调用时,this才会被赋值。如果你理解这个JavaScript原则,那么你就能深刻地理解this关键词。我们暂且将定义this的函数称为“THIS函数”。

尽管表面上看起来this指代的是定义它的对象,但只有当THIS函数被一个对象时,this才会被赋值。该值完全取决于调用THIS函数的对象。在大部分情况下,this储存的都是调用对象的值。然而少数情况下this储存的却不是调用对象的值,稍后我会讨论这些情况。

在全局作用域下使用this

当代码在浏览器里执行时,全局作用域里的所有全局变量和函数都在window对象里定义,所以在全局函数里使用this,它指代window对象并储存着该对象的值(如上文提到的一样,严格模式除外)。window对象是整个JavaScript程序或网页的主储存器。

var firstName = "Peter",
lastName = "Ally";
​
function showFullName () {//这个函数里的"this"会储存window对象的值//因为与变量firstName和lastName一样,showFullName()函数是在全局作用域里定义的console.log (this.firstName + " " + this.lastName);}
​
var person = {firstName   :"Penelope",lastName    :"Barrymore",showFullName:function () {//这行的"this"指代person对象,因为showFullName函数会被person对象调用console.log (this.firstName + " " + this.lastName);}
}
​
showFullName (); // Peter Ally​
​
//因为所有全局变量和函数都是在window对象里定义的,所以:
window.showFullName (); // Peter Ally​
​
//showFullName()方法定义于person对象里,它里面的"this"依旧指代person对象,所以:
person.showFullName (); // Penelope Barrymore

this最容易用错的情况

this关键词在下列情况下最容易被用错:

  • 当使用this的方法被“借用”时;
  • 当使用this的方法被赋值给变量时;
  • 当使用this的方法被用作回调函数时;
  • this被用于闭包-内部函数里时。

下文将探讨每种情况,并通过范例给出让this获取正确值的方法。

题外话:浅析“上下文环境”

JavaScript里的上下文环境与英语里句子的主语的含义相似:“约翰赢了比赛,却把钱还了回去。”句子的主语是约翰,我们可以说句子的上下文环境的就是约翰,因为这个句子关注的是约翰这个人。就连句子后半部分被省略的“他”,指的也是约翰这个先行词。在英文里可以用分号改变句子的主语,于此类似,函数可以在一个对象里定义并将其作为自己当前的上下文环境,也可以被其他对象调用,从而将上下文环境换成那个对象。

在JavaScript代码里可以做类似的事情:

var person = {firstName   :"Penelope",lastName    :"Barrymore",showFullName:function () {// “上下文环境”console.log (this.firstName + " " + this.lastName);}
}
​
//当调用在person对象上定义的showFullName方法时,其“上下文环境”就是person对象
//showFullName()方法里的this储存着person对象的值
person.showFullName (); // Penelope Barrymore​
​
​// If we invoke showFullName with a different object:​
//如果用其他对象来调用showFullName()方法:
​var anotherPerson = {firstName   :"Rohit",lastName    :"Khan"​
};
​
//可以使用apply方法将this的设为特定的值 - 稍微会继续讨论apply()方法
//无论哪个对象调用了this,this都会获取该对象的值,所以:
person.showFullName.apply (anotherPerson); // Rohit Khan​
​
//所以上下文环境现在就是anotherPerson对象,因为是它通过使用apply()方法调用了person.showFullName ()这个方法

总结起来就是:调用THIS函数的对象就是其上下文环境,但其他对象调用THIS函数就会变成其上下文环境。

当使用this的方法被用作回调函数时

当使用this的方法作为回调函数传给其他函数时,情况就有点棘手了。例如:

//创建一个包含clickHandler()方法的简单对象,当页面上的按钮被点击时可以使用
var user = {data:[{name:"T. Woods", age:37},{name:"P. Mickelson", age:43}],clickHandler:function (event) {var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // 0到1之间的随机整数//这行代码从data数组里随机获取名字(person's nam)和年龄(age)并输入console.log (this.data[randomNum].name + " " + this.data[randomNum].age);}
}
​
//button被封装在jQuery的美元符封装器($)里,所以可以直接作为jQuery对象使用
//因为button对象没有data属性,所以结果为undefined
$ ("button").click (user.clickHandler); //无法获取undefined名为“0”的属性

上方的代码中,按钮($(“button”))本身就是一个对象,我们将user.clickHandler()方法作为其click()方法的回调函数传入,所以user.clickHandler()方法里的this将不再指代user对象。现在this指代的是执行user.clickHandler()方法的对象。虽然this是在user对象里定义的,但是button对象调用了user.clickHandler()方法 - 确切的说,user.clickHandler()方法是在button对象的click()方法里被执行的。

需要注意的是,尽管我们是通过user.clickHandler()这种形式(必须这样子做,因为clickHandler()是在user对象里定义的方法)来调用clickHandler () 方法的,clickHandler () 方法被执行时其上下文环境是button对象,所以this现在指代的是button对象($(“button”))。

说了这么多,应该很清楚了:当上下文环境改变时,也就是说方法在一个对象里定义却到另外一个对象里执行时,this关键词不再指代原对象,而是指代调用该方法的对象。

当方法作为回调函数时,让this获取正确值的方式

如果要让this.data指代user对象的data属性,可以使用Bind ()Apply ()或者Call ()方法给this设置特定的值。

在我另一篇文章JavaScript程序员必学:JavaScript的Apply、Call和Bind方法里,详细地探讨了这些方法,并讲解了如何在各种容易出错的情况下使用他们正确设置this的值。这里就不重发一遍了。我觉得这篇文章作为JavaScript程序员都应该读一读,建议你好好看一下。

要解决前例的问题,可以使用bind()方法,所以

我们不这么写:

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

而是这样子将clickHandler()方法绑定到user对象:

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

到JSBin上查看运行结果

this被用于闭包时

另外一种this容易被用错的情况是使用闭包。一定要记住,闭包使用this关键词无法访问外部函数的this变量。函数的this变量只能被自身访问,其内部变量不行。例如:

var user = {tournament:"The Masters",data:[{name:"T. Woods", age:37},{name:"P. Mickelson", age:43}],clickHandler:function () {//在这里使用this.data是没问题的,因为“this”指代的是user对象而data是user对象的属性this.data.forEach (function (person) {//但在这个匿名函数(作为forEach方法的参数)里,“this”不再指代user对象//这个内部函数无法访问外部函数里的“this”console.log ("What is This referring to? " + this); //[object Window]​console.log (person.name + " is playing at " + this.tournament);// T. Woods is playing at undefined​// P. Mickelson is playing at undefined​})}
​
}
​
user.clickHandler(); // What is "this" referring to? [object Window]

匿名函数里的this无法访问外部函数的this,所以在非严格模式下其被绑定了window对象上。

在匿名函数里让this获取正确的值

在匿名函数里使用this,然后将函数传入为forEach()方法的参数,会出问题。解决这个问题可以用JavaScript里一种常用的手法。在将匿名函数传给forEach()方法前,将this赋值给其他变量:

var user = {tournament:"The Masters",data:[{name:"T. Woods", age:37},{name:"P. Mickelson", age:43}],clickHandler:function (event) {//要在“this”指代user对象时获取到它的值,我们必须将其值传给其他变量:将“this”的值传给theUserObj变量,以待后面使用var theUserObj = this;this.data.forEach (function (person) {//不用this.tournament,而用theUserObj.tournament​console.log (person.name + " is playing at " + theUserObj.tournament);})}
}
​
user.clickHandler();
// T. Woods is playing at The Masters​
//  P. Mickelson is playing at The Masters

值得注意的是,很多JavaScript开发者喜欢将变量命名为that(见下方例子),然后将this赋值给它。个人觉得使用“that”这个词太过简单粗暴,我尽量让命名体现this指代的对象,所以我才在上面的代码中使用theUserObj = this

// JavaScript使用者的常用手法
var that = this;

到JSBin上查看运行结果

当使用this的方法被赋值给变量时

如果将方法赋值给变量,与我们期望的不同,this的值绑定的会是另外一个对象。见下方例子:

//此处的data是全局变量
var data = [{name:"Samantha", age:12},{name:"Alexis", age:14}
];
​
var user = {//此处的data是user对象的属性data:[{name:"T. Woods", age:37},{name:"P. Mickelson", age:43}],showData:function (event) {var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // 0到1之间的随机整数​//这行代码由data数组里产生一个随机的person数据加到text字段里console.log (this.data[randomNum].name + " " + this.data[randomNum].age);}
}
​
//将user.showData赋值给变量
var showUserData = user.showData;
​
//当执行showUserData()函数时,控制台输出的值来自全局的data数组,而不是user对象的data数组
showUserData (); // Samantha 12 (来自全局的data数组)​

在方法被赋值给变量时让this获取正确的值

可以用bind()方法设置this的值来解决问题:

//将showData方法绑定到user对象上
var showUserData = user.showData.bind (user);
​
//因为this关键词绑定到了user对象上,现在得到的是user对象的值
showUserData (); // P. Mickelson 43

当使用this的方法被“借用”时

在JavaScript开发里借用其他对象的方法是很常见的行为,作为JavaScript开发者当然时不时会这样子做,以此来节省开发时间。我在另外一篇文章里深入剖析了如何借用其他对象的方法:JavaScript程序员必学:JavaScript的Apply、Call和Bind方法。

我们来看下方法被借用后,this怎么样指代对象:

//这里有两个对象,一个有叫avg()的方法而另外一个没有
//所以我们会让另外一个对象借用一下该方法
var gameController = {scores  :[20, 34, 55, 46, 77],avgScore:null,players :[{name:"Tommy", playerID:987, age:23},{name:"Pau", playerID:87, age:33}]
}
​
var appController = {scores:[900, 845, 809, 950],avgScore:null,avg:function () {var sumOfScores = this.scores.reduce (function (prev, cur, index, array) {return prev + cur;});​this.avgScore = sumOfScores / this.scores.length;}
}
​
//如果运行下方代码,gameController.avgScore属性就会被设置为由appController对象"scores"数组得出的平均分数//别运行下面这行代码,它只作展示用。让appController.avgScore的值为null
gameController.avgScore = appController.avg();

avg方法的this关键词不会指代gameController对象,而是指代appController对象,因为调用它的是appController对象。

方法被借用时让this获取正确的值

要解决问题,确保appController.avg () 里的this指代的是gameController对象,可以使用apply ()方法:

//注意使用的是apply()方法,所以第二个参数必须是数组-这些参数最后都会被传给appController.avg()方法
appController.avg.apply (gameController, gameController.scores);
​
//gameController对象上的avgScore属性成功设置,尽管avg()方法是从appController对象借过来的
console.log (gameController.avgScore); // 46.4​
​
//appController.avgScore依然未null,其值没有被更新,只有gameController.avgScore的值被更新了
console.log (appController.avgScore); // null

gameController对象借用了appController对象的avg()方法。appController.avg ()this的值会被设置为gameController对象,因为apply ()方法的第一个参数传入的是gameController对象。apply ()方法的首个参数会将this设置为特定的值。

到JSBin上查看运行结果

结束语

希望学习完本文,你就完全理解JavaScript里的this关键词了。要在各种情况下正确使用JavaScript的this关键词,你也已经掌握了方法 - bind、apply、call方法以及将this赋值给其他变量。

现在你应该知道,this的上下文环境一改变,有时就会很麻烦。当涉及到回调函数,或者方法由其他对象调用,又或者方法被其他对象借用时,会特别绕。反正你记住this储存的是调用THIS函数的对象的值就行了。

祝各位顺利,好好休息。编程快乐!

透彻理解并掌握JavaScript的this相关推荐

  1. (自己收藏)全面理解面向对象的 JavaScript

    全面理解面向对象的 JavaScript 前天 by 资深编辑 WnouM 评论(3) 有2727人浏览 收藏 javascript 面向对象 对象 类 原型 < >猎头职位: 上海:Ju ...

  2. 理解与使用Javascript中的回调函数

    在Javascript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用.既然函数实际上是对象:它们能被"存储"在变量中,能作为函数参数被传递,能在函数中被创建, ...

  3. 透彻理解Spring事务设计思想之手写实现

    2019独角兽企业重金招聘Python工程师标准>>> 前言 事务,是描述一组操作的抽象,比如对数据库的一组操作,要么全部成功,要么全部失败.事务具有4个特性:Atomicity(原 ...

  4. 【手写系列】透彻理解MyBatis设计思想之手写实现

    前言 MyBatis,曾经给我的感觉是一个很神奇的东西,我们只需要按照规范写好XXXMapper.xml以及XXXMapper.java接口.要知道我们并没有提供XXXMapper.java的实现类, ...

  5. 【手写系列】透彻理解Spring事务设计思想之手写实现

    事务,是描述一组操作的抽象,比如对数据库的一组操作,要么全部成功,要么全部失败.事务具有4个特性:Atomicity(原子性),Consistency(一致性),Isolation(隔离性),Dura ...

  6. 【JavaScript】理解与使用Javascript中的回调函数

    在Javascript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用.既然函数实际上是对象:它们能被"存储"在变量中,能作为函数参数被传递,能在函数中被创建, ...

  7. 一维数据高斯滤波器_透彻理解高斯混合模型

    高斯混合模型GMM是一个非常基础并且应用很广的模型.对于它的透彻理解非常重要. 本文从高斯分布开始逐步透彻讲解高斯混合模型 高斯分布 高斯分布有两个参数: μ = mean(数据的中心) σ2 =va ...

  8. 深入理解Delete(JavaScript)

    深入理解Delete(JavaScript) Delete  众所周知是删除对象中的属性. 但如果不深入了解delete的真正使用在项目中会出现非常严重的问题 (: Following 是翻译  ka ...

  9. rtosucos和linux区别,为什么我们需要uCos?带你透彻理解RTOS

    原标题:为什么我们需要uCos?带你透彻理解RTOS 与uCos见面还是大学的时候,老师让我为毕业设计选一个课题,要求有关嵌入式实时操作系统,于是开始在网上搜索,顺理成章的就发现了uCos,于是开始了 ...

最新文章

  1. python扫描器_7.python实现高效端口扫描器之nmap模块
  2. php mysql or_mysql条件查询and or使用方法及优先级实例分析
  3. Compiling: main.cpp /bin/sh: g++: not found
  4. 小学六年级能用计算机器,小学六年级信息技术测试题
  5. 概率和统计的matlab指令
  6. 支持Dubbo接口文档生成的工具!
  7. LinkedHashMap 实现缓存(LRU、FIFO、weakhashMap)
  8. 【经验分享】来到新公司,我所遇到的三重障碍
  9. window10安装python2.7_Windows10-python2.7安
  10. 用 JavaScript 验证只能输入数字,并做数字加总
  11. matlab二项式拟合,数据回归分析和拟合的Matlab实现
  12. str和unicode类
  13. Hadoop学习笔记—16.Pig框架学习
  14. 多核处理器_游戏爱好者的福音!AMD全新一代高性能多核处理器3950X
  15. .JQuery中的Ajax
  16. pycharm安装PyQT5教程
  17. 浅谈PMSM电机控制之Clark变换(详细推导及MATLAB仿真)
  18. 统计小写英文字母的个数 c语言,输入一行字符,统计英文字母,空格,和其他字符的个数...
  19. 福州大学数学与计算机科学学院复试名单,福州大学数学与计算机科学/软件学院2020年硕士研究生招生复试结果(专业型公示)...
  20. css html文字淡入淡出,Css淡入淡出

热门文章

  1. Pushed master to new branch origin/master
  2. CIO40: IT人之爬格子码字
  3. ArcGIS小知识(九)——坡度、剖面曲率、坡向、等高线
  4. 阿尔法小蛋机器人怎样_深入解密科大讯飞阿尔法小蛋智能云陪护机器人TYS1怎么样呢?评价如何?求助专业爆料评测...
  5. swift版QQ音乐播放器(一)
  6. 新股前瞻|深耕IT界17年,伊登软件仅是个“中间商”
  7. Json的FastJson与Jackson
  8. tomcat启动异常:子容器启动失败(a child container failed during start)
  9. 张小平 (为奥运冠军名字作诗)
  10. 【Java高级程序设计学习笔记】深入理解jdk动态代理