一篇文章带你彻底了解何为this!

209 阅读5分钟

前言

在错综复杂的代码结构中,我们总能在很多地方看到this这个关键字,所以总是容易混淆,今天我就用一篇文章带大家来彻底了解其功能和用法。

this

this是我们的函数运行环境指针,用直白的话来阐述其概念就是:你可以把this想象成一个指针,它总是指向某个对象,其指向的是调用函数的对象,接下来我们来看一个例子就明白了:

var x = 2;
var obj = {
    x:1,
    foo:function(){
        console.log(this,this.x)
    }
}

obj.foo() 

image.png

此时我们的打印结果为{ x: 1, foo: [Function: foo] } 1,也就是thisthis.name的打印结果,那就说明此时的this就等于obj,因为foo()函数是被obj调用的,所以this指向obj

我们再来看下一个例子:

<body>
<script>
var x = 2;
var obj = {
    x:1,
    foo:function(){
        console.log(this,this.x)
    }
}
// obj.foo() 
var foo = obj.foo;
foo();
</script>
</body>

此时我们把上面的函数调用注释掉了,将其作为值赋给了foo变量,然后我们再执行foo函数,这时我们打开浏览器查看会发现:

image.png

它this指向的是windows,且输出的是全局环境中的x,值为2。这又是为什么呢,因为我们要时刻牢牢记住一个概念函数被谁调用this就指向谁,但是在我们这并没有发生任何函数调用,它只是作为一个普通的函数被执行了,所以它的this就会指向window

这时我们还有一个小小的知识点就是,如果在严格模式下执行这个函数,那么它的this就会由window变为undefined,这是因为this它既是JS中的goodpart它也是badpart,既然你都作为普通函数执行了,你就完全没必要指向任何对象呀。所以我们在<Script>文件第一行加入:

"use strict"

然后再浏览器打开就会发现:

image.png

ok,接下来讲讲另一种this,就是通过new关键字构造函数的方式执行,此时this将会指向被实例化的对象,记住是实例化的对象本身,而不是变量名

function Person(name) {
// 调用此函数的name被赋值为传进来的参数name
    this.name = name; 
    console.log(this);
}
const person1 = new Person('Alice');
console.log(person1);

image.png

可以看到this打印的值为被实例化的对象本身

小结

现在我们来小结一下,我们目前为止共讲了3种this,分别是:

  • 作为对象的方法被调用,this指向调用函数的对象
  • 作为普通函数执行,指向window,严格模式下为undefined
  • new构造函数调用执行,this指向实例化的对象

从这三种this我们可以看出,this本身的值并不是固定的,它取决于函数的调用方式,所以它的绑定是在函数执行前的一霎那被调用对象和方式所决定,而并不是在函数被定义时。

主动指定this

既然this都是被动被赋值的,那我们能不能主动指定this呢?答案是当然可以的,请看下面的例子:

<script>
var name = "a"
var a = {
    name:"b",
    func1:function(){
        console.log(this.name)
    },
    func2:function(){
        setTimeout(function(){
            this.func1()
        },1000)
    }
}
a.func2();
</script>

image.png 这串代码会输出什么呢?答案是报错了,因为我们是执行func1(),此时它的this会指向全局,但是在全局中并没有定义func1()这个函数,此时我们就需要通过手段来指定this的指向。

.call()是函数对象原型上的方法,也就是Function().prototype.call,Function是所有函数的构造函数,其余所有函数都是它的实例,就如同我们可以直接通过一个const function = new Function()const arr = new Array()创建一个函数实例和数组实例一样。它的作用就是指定一个函数的this指向,这时我们来稍稍修改上面的代码:

    func2:function(){
        setTimeout(function(){
            this.func1()
        }.call(a),1000)
   }

我们在函数的外部添加.call(a)方法后,它就会将this指向对象a,此时在浏览器中打开:

image.png

除了.call()之外,我们还可以将函数修改成箭头函数,因为箭头函数很简单,就是为了方便我们使用的,所以它上面并不会带有this,那要怎么来确定调用它的函数是谁呢?虽然它本身没有this,但是它会继承其所在词法作用域的this,也就是func2()this a,这里的this.func1()也就变成了a.func()1

    func2:function(){
        setTimeout(() => {
            this.func1()
        },1000)
    }

此时,浏览器中也正确输出了b。因为它继承的是其所在词法作用域的this,而词法作用域是在编译的时候决定的,也就是说此时的this和之前提到的三种this并不一样,它并不是在函数执行前一刻被绑定的,而是在定义时就已经确定了

ok接下来我们介绍最后一种方法,就是提前保存this,先看代码:

<script>
    var name = "a";
    var a = {
        name:"b",
        func1:function(){
            console.log(this.name);
        },
        func2:function(){
        // 提前保存调用func2()的对象的this
            let_this = this;
            console.log("func2",this);
            setTimeout(function() { 
            // this丢失,使用提前保存的this
                _this.func1();
            },1000)
          }
        }
    a.func2();
</script>

我们在这里使用了一个变量来提前保存我们需要调用函数的对象的this,然后在调用函数时将其作为this值赋给它,使其本身的this丢失,从而执行我们想要指定的对象来调用函数,同样这里输出的也是b

小结

在这里我们同样介绍了三种方法,这三种主动指定this和一开始我们讲的执行前绑定this的最大区别就是,这三种做法都是提前指定好了this,再调用它,而不是在调用的前一刻才绑定

结语

熟练掌握this的功能对我们在写复杂的代码时有着很大的帮助,并且在面试时也常会考到,如果文章中有问题欢迎指出在评论区交流,希望能对你有帮助。