前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何理解JavaScript中的this

如何理解JavaScript中的this

作者头像
ZEHAN
发布2020-09-23 18:15:39
4.1K0
发布2020-09-23 18:15:39
举报
文章被收录于专栏:前端开发ZEHAN前端开发ZEHAN

JavaScript中的 this 对于初学者来说是个难点,对于老手也会困惑。之前有一个小伙伴一直问我this的相关问题,所以今天抽出点时间深入的带大家理解this。希望通过我的理解能够对正在处于对this困惑的你指引方法,让你再也不用怕JavaScript中的this了,让你明白在各种情况下使用this。JavaScript 的 this 关键词是很不一样,因为 JavaScript 本来就不是一门基于类的面向对象编程语言。this 就是一个指针,指向我们调用函数的对象。

this 本身原本很简单,总是指向类的当前实例,this 不能赋值。这前提是说 this 不能脱离 类/对象 来说,也就是说 this 是面向对象语言里常见的一个关键字。说的极端点,如果你编写的 JS 采用函数式写法,而不是面向对象式,你所有的代码里 this 会少很多,甚至没有。记住这一点,当你使用 this 时,你应该是在使用对象/类 方式开发,否则 this 只是函数调用时的副作用。

this关键词基础知识

首先你要知道JavaScript中所有的函数都有属性,就如对象有属性一样。函数执行时会获取this属性的值,此时this就是一个变量,储存着调用该函数的对象的值。

this这个引用总是指代对象并储存着它的值(只能指代一个对象),一般都在函数或者对象方法里使用,但是也能用在函数外的全局作用域里。需要注意的是,如果在函数里使用严格模式,全局函数里this的值就是undefined。而在匿名函数里则不会绑定任何对象。

假设在函数A里使用this,它就储存着调用函数A的对象的值。要获取调用函数A的对象的属性和方法,就需要用到this,特别是当我们不知道改对象的名称或者没有名称可以指代该对象。所以,需要用this作为一个快捷方式来指代“先行对象”,也就是调用函数的对象。

思考一下下面这段代码,它展示了如何在JavaScript中使用this:

var person = {
    firstName   :"Penelope",
    lastName    :"Barrymore",
    //因为下方的showFullName方法使用了“this”关键词而且该方法是在person对象里定义的,
    //那么“this”就会储存着person对象的值,因为该对象会调用showFullName()方法
    showFullName:function () {
    console.log (this.firstName + " " + this.lastName);
    }
    }
    person.showFullName (); // Penelope Barrymore

接着看一下下面很基础的jQuery范例代码,相信大家都见到过,这里面也用了this这个关键词:

$("button").click (function (event) {
    //$(this)会储存按钮对象($("button"))的值
    //因为click()方法是由该按钮对象调用的
    console.log ($ (this).prop ("name"));
});

JavaScript this关键词在jQuery里的语法形式是(this),它被用在一个匿名函数里面,而该函数则在按钮对象的click()方法里执行。(this)会被绑定到按钮对象,是因为jQuery库将(this)绑定到了调用click方法的对象中。所以尽管(this)在匿名函数里定义且该函数本身无法访问外部函数的this变量,(this)还是储存着jQuery按钮对象(“button”)的值。

需要注意的是,该按钮不但是HTML页面的DOM元素,而且是一个对象。因为我们将它封装在 jQuery $()函数里,在这种情况下它就是一个jQuery对象。

this 关键词使用误区

只有当定义this的函数被对象调用时,this才会被赋值。如果你理解这个JavaScript原则,那么你就能深刻地理解this关键词。我们暂且将定义this的函数称为“this函数”。

尽管表面上看起来this指代的是定义它的对象,但只有当THIS函数被一个对象时,this才会被赋值。该值完全取决于调用THIS函数的对象。在大部分情况下,this储存的都是调用对象的值。然而少数情况下this储存的却不是调用对象的值,稍后我会讨论这些情况。

全局作用域下this的使用

当代码在浏览器里执行时,全局作用域里的所有全局变量和函数都在window对象里定义,所以在全局函数里使用this,它指代window对象并储存着该对象的值(如上文提到的一样,严格模式除外)。window对象是整个JavaScript程序或网页的主储存器。

var firstName = "Peter",
lastName = "Ally";
​
function showFullName () {
    //这个函数里的"this"会储存window对象的值
    //因为与变量firstName和lastName一样,showFullName()函数是在全局作用域里定义的
    console.log (this.firstName + " " + this.lastName);
 }
​
var person = {
    firstName   :"Penelope",
    lastName    :"Barrymore",
    showFullName:function () {
    //这行的"this"指代person对象,因为showFullName函数会被person对象调用
        console.log (this.firstName + " " + this.lastName);
    }
}
​
showFullName (); // Peter Ally​
​
//因为所有全局变量和函数都是在window对象里定义的,所以:
window.showFullName (); // Peter Ally​
​
//showFullName()方法定义于person对象里,它里面的"this"依旧指代person对象,所以:
person.showFullName (); // Penelope Barrymore

this最容易用错的情况

this关键词在下列情况下最容易被用错:

  • 当使用this的方法被“借用”时;
  • 当使用this的方法被赋值给变量时;
  • 当使用this的方法被用作回调函数时;
  • 当this被用于闭包-内部函数里时。 下面我将通过代码例子一一探讨每种情况是如何发生的,同时给出让this获取正确值的方法。

函数可以在一个对象里定义并将其作为自己当前的上下文环境,也可以被其他对象调用,从而将上下文环境换成那个对象。

在JavaScript代码里可以做类似的事情:

var person = {
    firstName   :"Penelope",
    lastName    :"Barrymore",
    showFullName:function () {
        // “上下文环境”
        console.log (this.firstName + " " + this.lastName);
    }
}
​
//当调用在person对象上定义的showFullName方法时,其“上下文环境”就是person对象
//showFullName()方法里的this储存着person对象的值
person.showFullName (); // Penelope Barrymore​
​
//如果用其他对象来调用showFullName()方法:
​var anotherPerson = {
    firstName   :"Rohit",
    lastName    :"Khan"​
};
​
//可以使用apply方法将this的设为特定的值 - 稍微会继续讨论apply()方法
//无论哪个对象调用了this,this都会获取该对象的值,所以:
person.showFullName.apply (anotherPerson); // Rohit Khan​
​
//所以上下文环境现在就是anotherPerson对象,因为是它通过使用apply()方法调用了person.showFullName ()这个方法

总结:调用this函数的对象就是其上下文环境,但其他对象调用this函数就会变成其上下文环境。

使用this的方法被用作回调函数时 当使用this的方法作为回调函数传给其他函数时,这种情况就有点棘手。例如:

//创建一个包含clickHandler()方法的简单对象,当页面上的按钮被点击时可以使用

var user = {
    data:[
        {name:"T. Woods", age:37},
        {name:"P. Mickelson", age:43}
    ],
    clickHandler:function (event) {
        var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // 0到1之间的随机整数
        //这行代码从data数组里随机获取名字(person's nam)和年龄(age)并输入
        console.log (this.data[randomNum].name + " " + this.data[randomNum].age);
    }
}

//button被封装在jQuery的美元符封装器()里,所以可以直接作为jQuery对象使用//因为button对象没有data属性,所以结果为undefined ("button").click (user.clickHandler); //无法获取undefined名为“0”的属性上面的代码中,按钮(

需要注意的是,尽管我们是通过user.clickHandler()这种形式(必须这样子做,因为clickHandler()是在user对象里定义的方法)来调用clickHandler () 方法的,clickHandler () 方法被执行时其上下文环境是button对象,所以this现在指代的是button对象($(“button”))。

说了这么多,应该很清楚了:当上下文环境改变时,也就是说方法在一个对象里定义却到另外一个对象里执行时,this关键词不再指代原对象,而是指代调用该方法的对象。

当方法作为回调函数时,让this获取正确值的方式

如果要让this.data指代user对象的data属性,可以使用Bind (),Apply ()或者Call ()方法给this设置特定的值。

在我另一篇文章《JavaScript的Apply、Call和Bind方法》里,详细地探讨了这些方法,并讲解了如何在各种容易出错的情况下使用他们正确设置this的值。这里就不重发一遍了。我觉得这篇文章作为JavaScript程序员都应该读一读,建议你好好看一下。

要解决前例的问题,可以使用bind()方法,所以我们不这么写:

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

而是这样子将clickHandler()方法绑定到user对象:

$("button").click (user.clickHandler.bind (user)); // P. Mickelson 43

this被用于闭包时

另外一种this容易被用错的情况是使用闭包。一定要记住,闭包使用this关键词无法访问外部函数的this变量。函数的this变量只能被自身访问,其内部变量不行。例如:

var user = {
    tournament:"The Masters",
    data:[
        {name:"T. Woods", age:37},
        {name:"P. Mickelson", age:43}
    ],
    clickHandler:function () {
        //在这里使用this.data是没问题的,因为“this”指代的是user对象而data是user对象的属性
        this.data.forEach (function (person) {
            //但在这个匿名函数(作为forEach方法的参数)里,“this”不再指代user对象
            //这个内部函数无法访问外部函数里的“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,所以在非严格模式下其被绑定了window对象上。

在匿名函数里让this获取正确的值

在匿名函数里使用this,然后将函数传入为forEach()方法的参数,会出问题。解决这个问题可以用JavaScript里一种常用的手法。在将匿名函数传给forEach()方法前,将this赋值给其他变量:

var user = {
    tournament:"The Masters",
    data:[
        {name:"T. Woods", age:37},
        {name:"P. Mickelson", age:43}
    ],
    clickHandler:function (event) {
        //要在“this”指代user对象时获取到它的值,我们必须将其值传给其他变量:将“this”的值传给theUserObj变量,以待后面使用
        var theUserObj = this;
        this.data.forEach (function (person) {
            //不用this.tournament,而用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

值得注意的是,很多程序员喜欢将变量命名为that(见下方例子),然后将this赋值给它。个人觉得使用“that”这个词太过简单粗暴,我尽量让命名体现this指代的对象,所以我才在上面的代码中使用theUserObj = this。

// 程序员的常用手法
var that = this;

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) {
        var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // 0到1之间的随机整数
    ​
        //这行代码由data数组里产生一个随机的person数据加到text字段里
        console.log (this.data[randomNum].name + " " + this.data[randomNum].age);
    }
}
​
//将user.showData赋值给变量
var showUserData = user.showData;
​
//当执行showUserData()函数时,控制台输出的值来自全局的data数组,而不是user对象的data数组
showUserData (); // Samantha 12 (来自全局的data数组)​

在方法被赋值给变量时让this获取正确的值

我们可以用bind()方法设置this的值来解决问题:

//将showData方法绑定到user对象上
var showUserData = user.showData.bind (user);
​
//因为this关键词绑定到了user对象上,现在得到的是user对象的值
showUserData (); // P. Mickelson 43

当使用this的方法被“借用”时

在JavaScript开发里借用其他对象的方法是很常见的行为,作为JavaScript开发者当然时不时会这样子做,以此来节省开发时间。我在另外一篇文章里深入剖析了如何借用其他对象的方法:《JavaScript的Apply、Call和Bind方法》。

我们来看下方法被借用后,this是如何指代对象:

//这里有两个对象,一个有叫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 () {
        var sumOfScores = this.scores.reduce (function (prev, cur, index, array) {
            return prev + cur;
    });
    ​
        this.avgScore = sumOfScores / this.scores.length;
    }
}
​
//如果运行下方代码,gameController.avgScore属性就会被设置为由appController对象"scores"数组得出的平均分数

//别运行下面这行代码,它只作展示用。让appController.avgScore的值为null
gameController.avgScore = appController.avg();

avg方法的this关键词不会指代gameController对象,而是指代appController对象,因为调用它的是appController对象。

方法被借用时让this获取正确的值

要解决问题,确保appController.avg () 里的this指代的是gameController对象,this 可以被 call/apply 改变,所以我使用apply ()方法:

//注意使用的是apply()方法,所以第二个参数必须是数组-这些参数最后都会被传给appController.avg()方法
appController.avg.apply (gameController, gameController.scores);
​
//gameController对象上的avgScore属性成功设置,尽管avg()方法是从appController对象借过来的
console.log (gameController.avgScore); // 46.4​
​
//appController.avgScore依然未null,其值没有被更新,只有gameController.avgScore的值被更新了
console.log (appController.avgScore); // null

avg方法的this关键词不会指代gameController对象,而是指代appController对象,因为调用它的是appController对象。

this 可以被 call/apply 改变

call()/apply() 是函数调用的另外两种方式,两者的第一个参数都可以改变函数的上下文 this。call/apply 是 JS 里动态语言特性的表征。动态语言通俗的定义 程序在运行时可以改变其结构,新的函数可以被引进,已有的函数可以被删除,即程序在运行时可以发生结构上的变化。

var m1 = {
    message: 'This is A'
} 
var m2 = {
    message: 'This is B'
}  
 
function showMsg() {
    alert(this.message)
}
 
showMsg() // undefined
showMsg.call(m1) // 'This is A'
showMsg.call(m2) // 'This is B'

可以看到单独调用 showMsg 返回的是 undefined,只有将它绑定到具有 message 属性的对象上执行时才有意义。发挥想象力延伸下,如果把一些通用函数写好,可以任意绑定在多个类的原型上,这样动态的给类添加了一些方法,还节省了代码。这是一种强大的功能,也是动态语言的强表现力的体现。

**文章搬运于**码云笔记 原文链接:https://www.mybj123.com/1358.html

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-08-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • this关键词基础知识
  • this 关键词使用误区
  • 全局作用域下this的使用
  • this最容易用错的情况
  • 当方法作为回调函数时,让this获取正确值的方式
  • this被用于闭包时
  • 在匿名函数里让this获取正确的值
  • this的方法被赋值给变量时
  • 在方法被赋值给变量时让this获取正确的值
  • 当使用this的方法被“借用”时
  • 方法被借用时让this获取正确的值
  • this 可以被 call/apply 改变
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档