[译文]深入理解JavaScript的this关键字(二)

来源:互联网 发布:淘宝好评返现处罚条例 编辑:程序博客网 时间:2024/06/06 08:44

原文:Understand JavaScript’s “this” With Clarity, and Master It

上一篇文章:[译文]深入理解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 wooowvar 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 <em>this</em> keyword is bound to the user object​    showUserData (); // P. Mickelson 43

4 Fix this when borrowing methods

borrowing methods在JS开发者是一个普遍的实践。作为JS开发者,我们常常会碰到这个实践。偶尔,我们会使用这个节省时间的时间。如果像了解更多关于borrowing methods的知识,可以阅读我的深度文章: JavaScript’s Apply, Call, and Bind Methods are Essential for JavaScript Professionals

让我们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。

原创粉丝点击