欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

深入理解JavaScript的this关键字

程序员文章站 2023-10-18 18:56:10
this关键词最令人误解的场景和相关技巧 this关键字令人误解的场景包括:在borrow method中使用this;将一个使用this的方法赋给变量;在回调函数中使用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。