深入理解JavaScript的this关键字
this关键词最令人误解的场景和相关技巧
this关键字令人误解的场景包括:在borrow method中使用this;将一个使用this的方法赋给变量;在回调函数中使用this;在闭包中使用this。我们会依次查看这些场景,然后为了维护合适的this值提出相应的解决方案。
0 一个重要的笔记
在我们开始正式学习前关于context(上下文)的一点小知识
js中的context类似于英语中一个句子的主语:
john is the winner who returned the money.
john是句子中的主语;我们可以说句子的context是john,因为句子的焦点是在他上身。即使who这个代词也是指john,也就是,先行物。正如我们使用分号以切换句子的主语,我们可以有一个对象是当前的context,然后通过另一个对象来调用函数,将上下文切换成另一个对象。
在js代码中是这样的:
var person = { firstname: 'hhhha', lastname: 'wooow', showfullname: function() { console.log(this.firstname + ' ' + this.lastname); } } person.showfullname(); // hhhha wooow var anotherperson = { firstname: 'zyyyy', lastname: 'hhhhhha' } // change the context person.showfullname.apply(anotherperson); // zyyyy hhhhhha
1 fix this when used in a method passed as a callback
当然我们将一个方法(方法内使用this)将作为一个回调函数,事情就变量有些麻烦。比如下面这个例子:
// we have a simple object with a clickhandler method that we want to use when a button on the page is clicked var user = { data:[ {name:"t. woods", age:37}, {name:"p. mickelson", age:43} ], clickhandler:function (event) { var randomnum = ((math.random () * 2 | 0) + 1) - 1; // random number between 0 and 1 // this line is printing a random person's name and age from the data array console.log (this.data[randomnum].name + " " + this.data[randomnum].age); } } // the button is wrapped inside a jquery $ wrapper, so it is now a jquery object // and the output will be undefined because there is no data property on the button object $ ("button").click (user.clickhandler); // cannot read property '0' of undefined
在上述代码,由于button( $ (“button”))是一个对象,然后我们将user.clickhandler方法传递给它的click方法用作回调函数。我们知道在这个回调函数中,this不会再指向user对象。this现在指向另一个对象(user.clickhandler在这个对象中执行),因为this在user.clickhandler方法中被调用。调用user.clickhandler的对象的是button: user.clickhandler会在button的click方法中被调用。
即使我们现在通过user.clickhandler调用clickhandler()(我们不得不这么做,因为clickhandler这个方法定义在user上面),现在clickhandler这个方法在button上下文(context)这中被定义,这个context是this指向的对象。所以this现在指向$(“button”)对象。
在这个代码中,很明显,context改变了——当我们在其他对象上而不是原来对象上执行一个方法时,this关键字就不再指向原来的对象了,原来的对象是指this被定义的对象,它反而指向调用方法的对象了。
修复“方法被传递作为回调函数“引发的this问题的解决方案是:因为我们想让this.data指向user对象上的data属性,我们可以使用bind().apply(),call()方法专门指定this的值。
我已经写了篇详细的文章: javascript’s apply, call, and bind methods are essential for javascript professionals 关于这些方法,包括如何在这些令人误解的场景使用它们设置this的值。我不会再这里赘述这篇文章的内容,我建议你整篇文章,我认为这篇文章是一个js开发者必读的。
为了修复之前例子中的这个问题,我们可以这么使用bind方法:
$("button").click (user.clickhandler.bind (user)); // p. mickelson 43
我们已经将user.clickhandler方法和user对象绑定。
2 fix this inside closure
另一个this令人误解的例子是当我们使用一个闭包函数时。我们必须记住:闭包不能通过this关键词访问挖不不函数的this变量因为this变量只能通过函数自身访问,不能被内部函数访问。举个例子:
var user = { tournament:"the masters", data :[ {name:"t. woods", age:37}, {name:"p. mickelson", age:43} ], clickhandler:function () { // the use of this.data here is fine, because "this" refers to the user object, and data is a property on the user object. this.data.foreach (function (person) { // but here inside the anonymous function (that we pass to the foreach method), "this" no longer refers to the user object. // this inner function cannot access the outer function's "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,所以this是绑定全局window对象的,前提是严格模式不被使用。
在匿名函数中维护this值的解决方案:
为了解决上述问题,我们使用一种在js的普遍实践,然后在进入foreach之前,将this值赋给另一个变量。
var user = { tournament:"the masters", data :[ {name:"t. woods", age:37}, {name:"p. mickelson", age:43} ], clickhandler:function (event) { // to capture the value of "this" when it refers to the user object, we have to set it to another variable here: // we set the value of "this" to theuserobj variable, so we can use it later var theuserobj = this; this.data.foreach (function (person) { // instead of using this.tournament, we now use 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
很多js开发者喜欢使用一个变量that去设置this的值,像这样:var that = this;用什么变量名指代并不重要。但是,that变量名的使用令我感到尴尬,我尽量用一个名词去指代this指代的对象,所以我在前面的代码中这么使用:var theuserobj = this
3 fix this when method is assigned to a variable
如果我们将一个使用this的方法赋给一个对象,this的值会超出我们想象:它会绑定另一个对象。让我们看下面的代码:
// this data variable is a global variable var data = [ {name:"samantha", age:12}, {name:"alexis", age:14} ]; var user = { // this data variable is a property on the user object data :[ {name:"t. woods", age:37}, {name:"p. mickelson", age:43} ], showdata:function (event) { var randomnum = ((math.random () * 2 | 0) + 1) - 1; // random number between 0 and 1 // this line is adding a random person from the data array to the text field console.log (this.data[randomnum].name + " " + this.data[randomnum].age); } } // assign the user.showdata to a variable var showuserdata = user.showdata; // when we execute the showuserdata function, the values printed to the console are from the global data array, not from the data array in the user object // showuserdata (); // samantha 12 (from the global data array)
针对上述问题的解决方法是这样的:我们可以在bind方法中专门设定this的值以修复这个问题。
// bind the showdata method to the user object var showuserdata = user.showdata.bind (user); // now we get the value from the user object, because the this keyword is bound to the user object showuserdata (); // p. mickelson 43
4 fix this when borrowing methods
borrowing methods在js开发者是一个普遍的实践。作为js开发者,我们常常会碰到这个实践。偶尔,我们会使用这个节省时间的时间。
让我们this在borrow methods的context的关联性:
// we have two objects. one of them has a method called avg () that the other doesn't have // so we will borrow the (avg()) method 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; } } //if we run the code below, // the gamecontroller.avgscore property will be set to the average score from the appcontroller object "scores" array // don't run this code, for it is just for illustration; we want the appcontroller.avgscore to remain null gamecontroller.avgscore = appcontroller.avg();
avg方法的this关键字不会指向gamecontroller对象,她会指向appcontroller对象,因为它在appcontroller对象中被调用。
解决方案:
为了解决这个问题,确保appcontroller.avg()方法中的this指向gamecontroller,我们能够使用apply()方法,像下面这样:
// note that we are using the apply () method, so the 2nd argument has to be an array—the arguments to pass to the appcontroller.avg () method. appcontroller.avg.apply (gamecontroller); // the avgscore property was successfully set on the gamecontroller object, even though we borrowed the avg () method from the appcontroller object console.log (gamecontroller.avgscore); // 46.4 // appcontroller.avgscore is still null; it was not updated, only gamecontroller.avgscore was updated console.log (appcontroller.avgscore); // null
gamecontroller对象借用appcontroller的avg方法。appcontroller的avg方法的this值会被设为gamecontroller对象,因为我们会传递gamecontroller对象作为第一个参数给apply方法,apply方法的第一个参数总是会显示地设置this的值。
5 最后想说的话
现在你经有工具(bind,apply,call和保存this的值给一个变量)去任何一个场景中去征服js的this。
当context切换的时候,this这个关键词变得有些麻烦。你需要铭记于心的是:this总是指向调用this函数的对象。
好好学习,天天coding。
上一篇: 3ds Max制作超酷的外星科幻植物