新闻资讯

新闻资讯 通知公告

JavaScript中的this详解

编辑:009     时间:2020-02-18

前言

本文为学习过程中的this小节,作为一名JavaScript自学未成才的编程人员,还没从“原型继承”中回过神来(可以参考笔者上一篇文章《大话JavaScript对象》),又发现另一个让人难受的事实:JavaScript中的this是动态变化的!啥意思?this并非在定义时就确定好指向,而是在运行时才确定,this最终指向调用他的那个对象。this随着环境的变化而变化,这完全就是自然界中的变色龙。

紧抓中心思想:谁调用,this指向谁。下面通过以下几点来阐述这个思想:
1.直接调用函数
2.通过对象调用函数
3.通过bind、call、apply调用函数
4.通过new调用构造函数
5.class中的this指向
6.箭头函数中的this指向
7.class中带箭头函数的this指向


直接调用函数
function test() {
    console.log(this)
}
test()  // window

function strictTest() {
    'use strict'
    console.log(this)
}
strictTest()  // undefined

在严格模式下,this指向undefined,正常模式下指向window。思考下为什么正常模式会指向window?

先来看这样一个事实:

global this

this在全局作用域中是指向window的,而“function test”与“function strictTest”默认是加到全局this所指向的对象中的。换句话说,在全局环境中定义的函数默认与全局的this关联。同理,在全局作用域中调用的函数也默认与全局this关联。所以直接调用test函数与以下调用方式是等价的:

this.test() window.test() 

根据谁调用,this指向谁的思想指导,此时是window调用test函数,所以函数内部的this指向window。

通过对象调用
var obj = {
    a: function test() {
        console.log(this)
    }
}
obj.a()  // obj
var temp = obj.a
temp()  // window

a是obj对象的方法,通过obj调用a函数,函数内部的this指向obj。当重新用变量temp接收a函数,此时在全局环境中直接调用temp。上文说过,这种调用方式相当于this.temp()调用,而全局环境中的this指向window,所以也等价于window.temp()调用。此时是window调用temp函数,所以函数内部this指向window。

通过bind、call、apply调用函数
var obj = {}
function test() {
    return this
}

bind、call、apply

bind、call、apply这三个函数可以直接指定被调函数的调用对象,因为已经指定调用对象为obj,即,test函数是通过obj对象调用的,所以此时函数内部的this指向obj。

通过new调用构造函数
var that = undefined
function Test() {
    that = this
}
var t = new Test

构造函数

由于构造函数不能写return,此处用that来接收函数内部的this后再做比较。显然,构造函数内部的this指向构造函数创建的对象本身,这和我们的基本认知相符。

这里有个注意点:通过new命令调用的构造函数是无法与bind、call、apply这三个函数组合使用的。如果不通过new命令调用构造函数,而是当做普通函数调用,此时是可以与这三个函数组合使用的。验证如下:

无法组合使用
可以组合使用
class中的this指向

,class的本质还是function,但是毕竟写法不一样,class中的this指向谁?



var that = undefined
class ClassA {
    constructor() {
        that = this
    }
    foo() {
        return this
    }
    static staticFoo() {
        return this
    }
}
var a = new ClassA

class

“new ClassA”会调用ClassA的constructor(构造函数),此时this的指向为ClassA创建的对象本身,这点和上文中的通过new调用构造函数一致。
class中函数存储在class的原型中,此时foo函数通过a对象调用,所以函数内部的this指向a对象。
class中static函数存储在class中,此时staticFoo函数通过ClassA调用,所以函数内部的this指向ClassA。
以上结论同样适用于通过extends class创建的类,此处不再赘述。

箭头函数中的this指向

var that = undefined
var obj = {
    a: () => that = this
}
obj.a()

箭头函数

根据“谁调用,this指向谁”,此时obj调用a,为啥this不指向obj反而指向window?

箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。

也就是说:箭头函数没有自己的this,他的this是其作用域链上一层中的this。

回到刚才的例子,箭头函数所属作用域为obj对象,而obj对象所属作用域为全局作用域。所以此时通过“obj.a()”来调用,函数内部的this指向全局作用域中的this,也就是window。

再举个例子强化一下箭头函数中的this:


var that = undefined
var obj = {
    test() {
        return () => that = this
    }
}


箭头函数

“obj.test()()”调用方式中,箭头函数的作用域为test函数,而test函数的作用域为obj对象,所以箭头函数中的this指向obj对象。
“f()()”调用方式中,箭头函数的作用域为f函数(f函数是test函数的引用),f函数的作用域为全局作用域,所以箭头函数中的this指向window。

箭头函数中关于this的注意点:

由于 箭头函数没有自己的this指针,通过 call() 或 apply() 方法调用一个函数时,只能传递参数(不能绑定this---译者注),他们的第一个参数会被忽略。(这种现象对于bind方法同样成立---译者注)

也就是说,箭头函数无法通过bind、call、apply这三个函数改变this的指向。简单验证下:



var that = undefined
var obj = {
    a: () => that = this
}

箭头函数无法指定this

基于箭头函数中的this会指向作用域链上一层中的this,并且无法指定箭头函数中的this这两个特点,通常在定时器如“setTimeout”调用时采用箭头函数来填坑。

到这里已经完全掌握了箭头函数中的this吗?笔者脑洞开了一下,如果class与箭头函数组合会产生怎样的化学反应?

class中带箭头函数的this指向
var that = undefined
class A {
    test () {
        return () => that = this
    }
}
var a = new A

class中带箭头函数

“a.test()()”调用方式中,箭头函数的作用域为test函数,而test函数的作用域为“class A”创建的对象a,所以此时箭头函数内部的this指向a。
为啥“f()()”调用后that变成undefined?不应该是window吗?其实这里并没有违背箭头函数中的this指向作用域链上一层的this这个说法,来看一段MDN中关于class的描述:

类声明和类表达式的主体都执行在严格模式下。比如,构造函数,静态方法,原型方法,getter和setter都在严格模式下执行。

根本原因在于class中的函数是在严格模式下执行的,当用“f”接收“a.test”函数时,其实相当于在全局作用域这样写:


var f = function test() {
    'use strict'
    return () => that = this

}

文章开头我们说过,全局作用域严格模式下函数内部的this指向undefined,因此采用“f()()”方式调用时,箭头函数中的this指向undefined。


“this”,想说爱你不容易。

Have fun!



作者:01_Jack
链接:https://www.jianshu.com/p/0a6a38e1974d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。



郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

回复列表

相关推荐