初学JS,对this有些困惑,在此做一下总结,欢迎指正交流。
就和我们平常说话一样,“张三跑得飞快,因为他快迟到了”,注意这个他。我们也可以说“张三跑得飞快,因为张三快迟到了”,但是一般不这么说。同样,在JS里,我们用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",所以我们使用this不仅起到简化指代的作用,更可以让语句变得明确,就像我们用"他"来特指谈到的那个张三而不是别的张三。另外,在大多数时候我们不会事先知道调用该函数的对象叫什么名字,而可以通过this来获取该对象的属性和方法。
再来看一个简单例子:
var person = { firstName: "Penelope", lastName: "Barrymore", // "this"被用在下面的showFullName方法里,这个方法定义在person对象里 showFullName: function() { console.log(this.firstName + " " + this.lastName); } }; // "this"指向person对象,因为person对象调用了showFullName()方法 person.showFullName(); // Penelope Barrymore
一个简单的jQuery例子:
$("button").click(function(event) { // $(this)指向$("button")对象 // 因为button对象调用了click()方法 console.log($(this).prop("name")); });
在这里,之所以$(this)指向$("button")是因为jQuery把$(this)绑定到触发click方法的对象上,虽然实际上定义这个$(this)的匿名函数是无法获取其外部函数的"this"的值的,我们会在后文加以解释。
全局作用域中的this
当代码在浏览器中执行时,所有的全局变量和函数都定义在window对象下,它是整个javascript应用或页面的容器。因此当我们在一个全局函数中使用this时,它指向window对象。
var firstName = "Peter", lastName = "Ally"; function showFullName() { // 此函数中的"this"会指向window对象 // 因为showFullName()定义在全局作用域下,和firstName、lastName一样 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 // 这里"this"指向person对象,因此 person.showFullName(); // Penelope Barrymore
那些"不守规矩"的this
我们知道JS里的函数也是对象,所以函数可以有属性。在一个函数执行时,它会得到this属性(PS:随着深入学习,这句话不对,this并不是函数对象的属性,而是执行上下文的一个属性,this的值thisValue在进入上下文的时候确定,并在上下文运行期间永久不变,想要深入理解的同学可以去看汤姆大叔的深入理解javascript系列),其值为调用该函数的对象,我们姑且称这个函数为"this函数"。this总是指向一个对象,并且常常被用在函数或方法里,当然它也可以在函数外部全局作用域里使用。注意,直到this函数被调用时,this才被赋值。在大部分情况下,this指向调用this函数的对象,然而在某些情况下会有例外。
1.作为被传递的回调函数里的this
var user = { data: [{ name: "T. Woods", age: 37 }, { name: "P. Mickelson", age: 43 }], clickHandler: function(event) { var randomNum = Math.floor(Math.random() * 2); // 0或1的随机数 // 从data数组中读取name和age console.log(this.data[randomNum].name + " " + this.data[randomNum].age); } }; // 结果为undefined,因为button对象中不存在data属性 $("button").click(user.clickHandler); // Cannot read property '0' of undefined
在这里,$("button")是一个对象,我们把user.clickHandler方法作为回调函数传递给它的click()方法,此时user.clickHandler方法里的this不再指向user对象,而是指向调用user.clickHandler方法的对象,即button对象。注意虽然我们使用user.clickHandler来调用clickHandler()方法(毕竟clickHandler是定义在user下的方法),clickHandler()方法本身是被button对象调用的,所以this指向button对象。
解决方法
现在我们很想让this.data能够表示user对象的data属性,我们可以使用Bind(),Apply()和Call()方法来专门改变this的值。
在这个例子里,我们使用bind方法,将这一行
$("button").click(user.clickHandler);
改为以下这行,把clickHandler方法绑定到user对象
$("button").click(user.clickHandler.bind(user)); // P. Mickelson 43
2.闭包中的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) { // 但在这个内部匿名函数里,"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对象上。
解决方法
一个惯例方法是在进入forEach方法之前,我们把this的值赋给另一个变量。
var user = { tournament: "The Masters", data: [{ name: "T. Woods", age: 37 }, { name: "P. Mickelson", age: 43 }], clickHandler: function(event) { // 趁着在这里"this"还是指向user对象的,我们此时把"this"的值赋给that变量 var that = this; this.data.forEach(function(person) { // 把this.tournament改为that.tournament,由于作用域链,内部函数可以访问外部函数的that变量 console.log(person.name + " is playing at " + that.tournament); }) } }; user.clickHandler(); // T. Woods is playing at The Masters // P. Mickelson is playing at The Masters
这个that变量可以是任意名字,一般我们取名为that。
3.当方法被赋值给一个变量时的this
// 全局变量data var data = [{ name: "Samantha", age: 12 }, { name: "Alexis", age: 14 }]; var user = { // user的属性data data: [{ name: "T. Woods", age: 37 }, { name: "P. Mickelson", age: 43 }], showData: function(event) { var randomNum = Math.floor(Math.random() * 2); 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对象的data showUserData(); // P. Mickelson 43
4.借用函数时的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借用avg()方法,求得自己scores的平均值 // 同时不改变appController的avgScore // 下面这行肯定是行不通的 gameController.avgScore = appController.avg();
解决方法
我们可以使用apply()方法
// 注意我们在使用apply()方法,所以第二个参数必须是数组,它包含要传递给appController.avg()的参数 appController.avg.apply(gameController, gameController.scores); // 成功得到gameController对象的avgScore的值,虽然借用的是appController对象的avg()方法 console.log(gameController.avgScore); // 46.4 // appController.avgScore仍然为null console.log(appController.avgScore); // null
译自http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/