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

原文: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 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 <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。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值