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关键字的核心
下面这条规则可以帮助你彻底搞懂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方法。
针对下面这行代码:
$ ("button").click (user.clickHandler);
我们可以用bind方法把clickHandler绑定的user对象上:
$("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的值。
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
结语
希望您在文中有所收获。现在你可以使用文中介绍的绝招(bind方法,apply方法,call方法,以及把this赋值给 一个变量)来对付跟this相关的任何问题。
正如已经了解到的,this在上下文改变、被作为回调函数使用、被不同的对象调用、或者方法被借用的情况下,this将一直指向调用当前方法的对象。
原文:http://www.cnblogs.com/bboyxiang/p/5225663.html