清晰理解JavaScript的 “this” 并掌握它

无论新入行的开发者,还是老手,也时常会被关键词“this”所迷惑。这篇文章的目标就是全面地解释this。当你读完这篇文章的时候,this将不再是你在JavaScript领域中的一个难题。我们将会在每个例子中了解如何使用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.firstName和person.lastName,这段代码会变得模糊不清:如果已经有一个全局变量person,当我们调用person.firstName的时候可能会访问到全局变量person中的属性,同时也给排除故障带来了困难。所以我们运用this来使得我们的代码美观的同时,还更为精确。

就像例句中的代词“他”指代着它的对象,this也指代着使用它的函数中的执行对象。this不但指代着它的对象,同时还包含着指代目标对象的值。就像代词一样,this也可以理解为语境中调出对象的捷径。

JavaScript关键词this基础知识

首先,所有JavaScript的函数都有属性,就像所有的对象都有属性一样。当一个函数被调用时,它就会拉取this属性:一个变量包含了函数执行时对象的数值。

this只会调用单个对象((并包含该目标的值)。注意,当我们运用到严格模式的时候,this在全局函数和匿名函数中保留未定义的值是不会上行到任何对象的。

当this在某个函数(假设函数A)中被调用时,它包含了被函数A调用对象的值。我们需要通过this来获取被调用对象的属性,特别是当我们调用对象没有名字的时候。实际上,this也仅仅是一个为调用对象提供的捷径。

再来看一次this最基本的例子:

varperson = {

  firstName:"Penelope",

  lastName:"Barrymore",

  //当this在“showFullName”函数中被调用,且该函数被定义在了person对象中

  // this就会包含了对象person的值因为person在showFullName被调用了

  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语法中的$(this),与JavaScript中关键字this有一样的功能。这里的$(this)运用在了一个匿名函数中,并且这个匿名函数在按钮click()上。$(this)上行到了按钮对象,是因为jQuery库中,$(this)被绑定在了被click()调用的对象上。因此,即使$(this)在匿名函数中被定义了,它也会含有jQuery按钮($(“button”))对象的值。

注意按钮在HTML中属于DOM元素,同时也是一个对象,在上述例子中它就是一个jQuery对象因为我们把它包装在jQuery $()函数中

有关this的“原来如此”

如果你了解下面这个规则,你就会非常清晰的了解this: this只会在定义this的函数调用了某个对象时才会被附上值。这里我们就把定义this的函数称为“this函数”吧。

即使看上去this出现在了对象被定义的地方,this在“this函数”被调用前都不会被真正地附上值,而且它的值仅仅决定于被“this函数”调用的对象。绝大多数情况下,this都会附上被调用对象的值,然而在少量情况下,this不会附上被调用对象的值。稍后的文章中会提到。

this用在全局作用域中

当代码运行在全局作用域中,所有全局变量和函数都被定义在了window对象中,因此当我们将this运用在全局函数中时,它指代(并含有)页面JavaScript主容器window对象的值(不包括严格模式,在之前有提到过)。

例如:

var firstName = "Peter",

lastName = "Ally";

function showFullName () {

  console.log(this.firstName + " " + this.lastName);

}

//这个函数里的this就含有window对象的值

//因为showFullName ()函数被定义为了全局函数,就像firstName和lastName​一样

//下面的this指代了person对象

//因为showFullName()函数被定义在了person对象中

var person = {

  firstName:"Penelope",

  lastName:"Barrymore",

  showFullName:function() {

    console.log(this.firstName + " " + this.lastName);

  }

}

​showFullName (); // Peter Ally​

​// window对象是所有全局变量和全局函数被定义的地方,所以:

window.showFullName ();  //输出Peter Ally​

​//在对象person中定义的函数showFullName()中的this依旧指代 对象person

person.showFullName();  //输出PenelopeBarrymore

this最迷惑人的几个地方

以下几种情况是this最为迷惑的情况:当我们借用函数中包含了this,当我们用含有this的方法定义变量,当一个函数用this去传递回调函数和当this出现在闭包里的时候。我们将会一一举例说明并弄清楚this在这些情况下的用法。

一、当this去传递回调函数

当我们使用一个带有this的方法作为一个参数出现在回调函数中的时候,事情将会变得复杂:

//下面有一个单一对象,还有一个会被页面按钮触发的方法clickHandler

var user = {

  data:[

    {name:"T. Woods", age:37},

    {name:"P. Mickelson", age:43}

  ],

  clickHandler:function(event) {

    varrandomNum = ((Math.random () * 2 | 0) + 1) - 1;  // 0到1之间的随机数

​    //下面这一行代码会在上面的data数组中随机抽取人名和年龄

    console.log(this.data[randomNum].name + " " + this.data[randomNum].age);

  }

}

//按钮被包装在了jQuery里面,所以现在它是一个jQuery对象

//但是会输出undefined因为在按钮对象上没有任何数据

$("button").click (user.clickHandler);  //报错:Cannot read property '0' of undefined

在以上的代码中,因为按钮($(“button”))本身就是一个对象,这里将方法user.clickHandler作为了回调函数传递数据,我们知道当this在user.clickHandler方法中时,它将不会指代对象user。this仅会指代user.clickHandler里面的对象,因为this被定义在了这个方法里面。而user.clickHandler被按钮所调用,所以user.clickHandler将会在按钮的点击时被执行。

注意到即使我们利用代码user.clickHandler调用了clickHandler()方法,这个方法本身将会被按钮调用,而且在这个语境中,this指代对象是按钮对象($(“button”))。

在这里我们可以很明显的看到语境的变化:当我们在其他对象上执行一个方法,而不是在这个对象当初被定义的地方的时候,this将不会指代原来的对象,而是会指代着被定义this的方法所调用的对象。

当我们想让this.data指代对象data中的属性时,我们可以用到bind(),apply()和call()来对this的值做出指定

要解决这个问题,我们可以使用方法bind():

把下面这行代码:

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

改写成:

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

二、当this在闭包中

另外一个容易混乱的地方就是当this用在了闭包中。非常重要的一点,闭包是没办法获取到闭包外其它函数中的this,因为this只会被定义它的函数本身所获取。

varuser = {

  tournament:"The Masters",

  data: [

    {name:"T. Woods", age:37},

    {name:"P. Mickelson", age:43}

  ],

  clickHandler:function () {

  //这里运用this.data是没有问题的

  //因为this指代了对象user,所以this拥有了对象user的属性

    this.data.forEach(function (person) {

      //但是在这个匿名函数中,this不再指代对象user​

      //因为闭包没办法获取外在函数中的this的值

      console.log ("What is This referringto? " + this);  //​ window对象

      console.log (person.name + " isplaying at " + this.tournament);

      // T. Woods is playing at undefined​

      // P. Mickelson is playing at undefined​

    })

  }

}

user.clickHandler();//现在this就指代了window对象

在匿名函数中的this不能获取外在函数中this的值,所以它上行到了全局对象window。

若要修正这段代码,我们只需要在进入函数forEach之前将this的值赋值到另外一个变量上:

var user = {

  tournament:"TheMasters",

  data:[

    {name:"T. Woods", age:37},

    {name:"P. Mickelson", age:43}

  ],

  clickHandler:function(event) {

  //若要获取当this还指代着对象user时候的值,,我们要在这里设置另外一个变量

  //把this的值赋值到了theUserObj变量上

  vartheUserObj = this;

  this.data.forEach(function (person) {

    //我们现在使用的是theUserObj.tournament​

    console.log (person.name + " isplaying at " + theUserObj.tournament);

    })

  }

}

user.clickHandler();  //输出T. Woods is

playing at The Masters​  //输出P. Mickelsonis playing at The Masters

三、当含有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) {

  varrandomNum = ((Math.random () * 2 | 0) + 1) - 1;  //0到1的随机数字

//这一行代码会在data数组中随机抽取并输出一个人名

 console.log(this.data[randomNum].name + " " + this.data[randomNum].age);

  }

}

​//将user.showData定义到一个变量中

var showUserData = user.showData;

​//当执行showUserData函数的时候,控制台输出值是从全局变量数组data中抽取出来的

//而不是对象user中的data数组

showUserData();  // Samantha 12 (从全局变量数组抽取)​

要修改这段代码,我们也可以用到绑定值的方法bind()将this赋值:

//将showData方法绑定在了对象user上

var showUserData = user.showData.bind(user);

​//现在我们可以获取user对象的值了,因为this上行到了user对象上

showUserDat();  //输出P. Mickelson 43

四、当this出现在借用函数中

借用函数在JavaScript开发中十分常见,作为一个JavaScript开发员,我们会时常见到这样的例子。这也是我们需要掌握的一个地方。

让我们看下当this出现在借用方法的情况:

//这里有两个对象,其中一个包含了avg()函数,另一个则没有

//所以我们借用了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 () {

    varsumOfScores = this.scores.reduce (function (prev, cur, index, array) {

    return prev + cur;

  });

  this.avgScore= sumOfScores / this.scores.length;

  }

}

//如果我们运行下面这段代码

// gameController.avgScore属性将会被赋上对象appController里scores数组里的值

gameController.avgScore= appController.avg();

函数avg()里的this将不会指代对象gameController,而会指代appController对象,因为appController对象被调用了。

要想修改这段代码,使appController.avg()里的this指代gameController,我们可以用到apply()方法:

//这里我们用到了apply(),

//所以gameController.scores必须是一个传递数据到appController.avg()方法的数组

appController.avg.apply (gameController,gameController.scores);

​// avgScore属性被成功的设置到了对象gameController

//即使我们借用了对象appController中的方法

console.log (gameController.avgScore); //46.4​

​//appController.avgScore仍然保持空值null,它并没有被更新

//仅仅只有gameController.avgScore被更新了

console.log(appController.avgScore);   // null

对象gameController借用了对象appController中的方法avg()。在方法appController.avg()里的this被设置指代对象gameController因为我们将gameController当作了apply()方法中的第一个参数,在apply()方法中第一个参数总会设置this的值

结束语:

我希望通过读完这篇文章之后,你们可以拥有足够的只是去应对JavaScript中的关键词this。一定要记住this只会被赋上“this函数”所调用的对象的值。

祝你们可以享受编程的乐趣!

译自:

http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容