美文网首页JavaScript
清晰理解JavaScript的 “this” 并掌握它

清晰理解JavaScript的 “this” 并掌握它

作者: Ryan57 | 来源:发表于2017-04-20 11:22 被阅读177次

    无论新入行的开发者,还是老手,也时常会被关键词“this”所迷惑。这篇文章的目标就是全面地解释this。当你读完这篇文章的时候,this将不再是你在JavaScript领域中的一个难题。我们将会在每个例子中了解如何使用this,包括最棘手最难以捉摸的部分。

    我们使用this类似于我们在日常交谈中使用代词一样。我们会写到“约翰跑得很快因为‘他’正在赶火车”。

    注意到这里的“他”。我们也可以写成“约翰跑得很快因为‘约翰’正在赶火车”。我们一般不会重复使用人名“约翰”,我们如果这样做了,我们的家人和朋友肯定会觉得我们脑子出了问题。在同样优雅的语言,JavaScript中,我们运用“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,当我们调用person.firstName的时候可能会访问到全局变量person中的属性,同时也给排除故障带来了困难。所以我们运用this来使得我们的代码美观的同时,还更为精确。

    就像例句中的代词“他”指代着它的对象,this也指代着使用它的函数中的执行对象。this不但指代着它的对象,同时还包含着指代目标对象的值。就像代词一样,this也可以理解为语境中调出对象的捷径。

    JavaScript关键词this基础知识

    首先,所有JavaScript的函数都有属性,就像所有的对象都有属性一样。当一个函数被调用时,它就会拉取this属性:一个变量包含了函数执行时对象的数值。

    this只会调用单个对象((并包含该目标的值)。注意,当我们运用到严格模式的时候,this在全局函数和匿名函数中保留未定义的值是不会上行到任何对象的。

    当this在某个函数(假设函数A)中被调用时,它包含了被函数A调用对象的值。我们需要通过this来获取被调用对象的属性,特别是当我们调用对象没有名字的时候。实际上,this也仅仅是一个为调用对象提供的捷径。

    再来看一次this最基本的例子:

    varperson = {

      firstName:"Penelope",

      lastName:"Barrymore",

      //当this在“showFullName”函数中被调用,且该函数被定义在了person对象中

      // this就会包含了对象person的值因为person在showFullName被调用了

      showFullName ()​

      showFullName: function () {

        console.log(this.firstName + " " + this.lastName);

      }

    }

    person.showFullName();//输出Penelope Barrymore

    同时也来看看jQuery下this的例子:

    //一段非常普通的jQuery代码

    $ ("button").click (function(event) {

      // $(this)将会附有按钮($("button"))对象的值

      ​//因为按钮对象被click ()函数调用了

      console.log($ (this).prop ("name"));

    });

    jQuery语法中的$(this),与JavaScript中关键字this有一样的功能。这里的$(this)运用在了一个匿名函数中,并且这个匿名函数在按钮click()上。$(this)上行到了按钮对象,是因为jQuery库中,$(this)被绑定在了被click()调用的对象上。因此,即使$(this)在匿名函数中被定义了,它也会含有jQuery按钮($(“button”))对象的值。

    注意按钮在HTML中属于DOM元素,同时也是一个对象,在上述例子中它就是一个jQuery对象因为我们把它包装在jQuery $()函数中

    有关this的“原来如此”

    如果你了解下面这个规则,你就会非常清晰的了解this: this只会在定义this的函数调用了某个对象时才会被附上值。这里我们就把定义this的函数称为“this函数”吧。

    即使看上去this出现在了对象被定义的地方,this在“this函数”被调用前都不会被真正地附上值,而且它的值仅仅决定于被“this函数”调用的对象。绝大多数情况下,this都会附上被调用对象的值,然而在少量情况下,this不会附上被调用对象的值。稍后的文章中会提到。

    this用在全局作用域中

    当代码运行在全局作用域中,所有全局变量和函数都被定义在了window对象中,因此当我们将this运用在全局函数中时,它指代(并含有)页面JavaScript主容器window对象的值(不包括严格模式,在之前有提到过)。

    例如:

    var firstName = "Peter",

    lastName = "Ally";

    function showFullName () {

      console.log(this.firstName + " " + this.lastName);

    }

    //这个函数里的this就含有window对象的值

    //因为showFullName ()函数被定义为了全局函数,就像firstName和lastName​一样

    //下面的this指代了person对象

    //因为showFullName()函数被定义在了person对象中

    var person = {

      firstName:"Penelope",

      lastName:"Barrymore",

      showFullName:function() {

        console.log(this.firstName + " " + this.lastName);

      }

    }

    ​showFullName (); // Peter Ally​

    ​// window对象是所有全局变量和全局函数被定义的地方,所以:

    window.showFullName ();  //输出Peter Ally​

    ​//在对象person中定义的函数showFullName()中的this依旧指代 对象person

    person.showFullName();  //输出PenelopeBarrymore

    this最迷惑人的几个地方

    以下几种情况是this最为迷惑的情况:当我们借用函数中包含了this,当我们用含有this的方法定义变量,当一个函数用this去传递回调函数和当this出现在闭包里的时候。我们将会一一举例说明并弄清楚this在这些情况下的用法。

    一、当this去传递回调函数

    当我们使用一个带有this的方法作为一个参数出现在回调函数中的时候,事情将会变得复杂:

    //下面有一个单一对象,还有一个会被页面按钮触发的方法clickHandler

    var user = {

      data:[

        {name:"T. Woods", age:37},

        {name:"P. Mickelson", age:43}

      ],

      clickHandler:function(event) {

        varrandomNum = ((Math.random () * 2 | 0) + 1) - 1;  // 0到1之间的随机数

    ​    //下面这一行代码会在上面的data数组中随机抽取人名和年龄

        console.log(this.data[randomNum].name + " " + this.data[randomNum].age);

      }

    }

    //按钮被包装在了jQuery里面,所以现在它是一个jQuery对象

    //但是会输出undefined因为在按钮对象上没有任何数据

    $("button").click (user.clickHandler);  //报错:Cannot read property '0' of undefined

    在以上的代码中,因为按钮($(“button”))本身就是一个对象,这里将方法user.clickHandler作为了回调函数传递数据,我们知道当this在user.clickHandler方法中时,它将不会指代对象user。this仅会指代user.clickHandler里面的对象,因为this被定义在了这个方法里面。而user.clickHandler被按钮所调用,所以user.clickHandler将会在按钮的点击时被执行。

    注意到即使我们利用代码user.clickHandler调用了clickHandler()方法,这个方法本身将会被按钮调用,而且在这个语境中,this指代对象是按钮对象($(“button”))。

    在这里我们可以很明显的看到语境的变化:当我们在其他对象上执行一个方法,而不是在这个对象当初被定义的地方的时候,this将不会指代原来的对象,而是会指代着被定义this的方法所调用的对象。

    当我们想让this.data指代对象data中的属性时,我们可以用到bind(),apply()和call()来对this的值做出指定

    要解决这个问题,我们可以使用方法bind():

    把下面这行代码:

    $ ("button").click(user.clickHandler);

    改写成:

    $ ("button").click(user.clickHandler.bind (user));

    二、当this在闭包中

    另外一个容易混乱的地方就是当this用在了闭包中。非常重要的一点,闭包是没办法获取到闭包外其它函数中的this,因为this只会被定义它的函数本身所获取。

    varuser = {

      tournament:"The Masters",

      data: [

        {name:"T. Woods", age:37},

        {name:"P. Mickelson", age:43}

      ],

      clickHandler:function () {

      //这里运用this.data是没有问题的

      //因为this指代了对象user,所以this拥有了对象user的属性

        this.data.forEach(function (person) {

          //但是在这个匿名函数中,this不再指代对象user​

          //因为闭包没办法获取外在函数中的this的值

          console.log ("What is This referringto? " + this);  //​ window对象

          console.log (person.name + " isplaying at " + this.tournament);

          // T. Woods is playing at undefined​

          // P. Mickelson is playing at undefined​

        })

      }

    }

    user.clickHandler();//现在this就指代了window对象

    在匿名函数中的this不能获取外在函数中this的值,所以它上行到了全局对象window。

    若要修正这段代码,我们只需要在进入函数forEach之前将this的值赋值到另外一个变量上:

    var user = {

      tournament:"TheMasters",

      data:[

        {name:"T. Woods", age:37},

        {name:"P. Mickelson", age:43}

      ],

      clickHandler:function(event) {

      //若要获取当this还指代着对象user时候的值,,我们要在这里设置另外一个变量

      //把this的值赋值到了theUserObj变量上

      vartheUserObj = this;

      this.data.forEach(function (person) {

        //我们现在使用的是theUserObj.tournament​

        console.log (person.name + " isplaying at " + theUserObj.tournament);

        })

      }

    }

    user.clickHandler();  //输出T. Woods is

    playing at The Masters​  //输出P. Mickelsonis playing at The Masters

    三、当含有this的函数被用做定义变量

    当我们用函数去定义一个变量时,this会上行到其他对象上:

    //这里的变量data是一个全局变量

    var data = [

      {name:"Samantha",age:12},

      {name:"Alexis",age:14}

    ];

    var user = {

    //这里的变量data是对象user的属性

      data:[

        {name:"T.Woods", age:37},

        {name:"P.Mickelson", age:43}

      ],

      showData:function(event) {

      varrandomNum = ((Math.random () * 2 | 0) + 1) - 1;  //0到1的随机数字

    //这一行代码会在data数组中随机抽取并输出一个人名

     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对象的值了,因为this上行到了user对象上

    showUserDat();  //输出P. Mickelson 43

    四、当this出现在借用函数中

    借用函数在JavaScript开发中十分常见,作为一个JavaScript开发员,我们会时常见到这样的例子。这也是我们需要掌握的一个地方。

    让我们看下当this出现在借用方法的情况:

    //这里有两个对象,其中一个包含了avg()函数,另一个则没有

    //所以我们借用了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 () {

        varsumOfScores = this.scores.reduce (function (prev, cur, index, array) {

        return prev + cur;

      });

      this.avgScore= sumOfScores / this.scores.length;

      }

    }

    //如果我们运行下面这段代码

    // gameController.avgScore属性将会被赋上对象appController里scores数组里的值

    gameController.avgScore= appController.avg();

    函数avg()里的this将不会指代对象gameController,而会指代appController对象,因为appController对象被调用了。

    要想修改这段代码,使appController.avg()里的this指代gameController,我们可以用到apply()方法:

    //这里我们用到了apply(),

    //所以gameController.scores必须是一个传递数据到appController.avg()方法的数组

    appController.avg.apply (gameController,gameController.scores);

    ​// avgScore属性被成功的设置到了对象gameController

    //即使我们借用了对象appController中的方法

    console.log (gameController.avgScore); //46.4​

    ​//appController.avgScore仍然保持空值null,它并没有被更新

    //仅仅只有gameController.avgScore被更新了

    console.log(appController.avgScore);   // null

    对象gameController借用了对象appController中的方法avg()。在方法appController.avg()里的this被设置指代对象gameController因为我们将gameController当作了apply()方法中的第一个参数,在apply()方法中第一个参数总会设置this的值

    结束语:

    我希望通过读完这篇文章之后,你们可以拥有足够的只是去应对JavaScript中的关键词this。一定要记住this只会被赋上“this函数”所调用的对象的值。

    祝你们可以享受编程的乐趣!

    译自:

    http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/

    相关文章

      网友评论

        本文标题:清晰理解JavaScript的 “this” 并掌握它

        本文链接:https://www.haomeiwen.com/subject/aaokzttx.html