前言
在错综复杂的代码结构中,我们总能在很多地方看到this
这个关键字,所以总是容易混淆,今天我就用一篇文章带大家来彻底了解其功能和用法。
this
this
是我们的函数运行环境指针,用直白的话来阐述其概念就是:你可以把this想象成一个指针,它总是指向某个对象,其指向的是调用函数的对象,接下来我们来看一个例子就明白了:
var x = 2;
var obj = {
x:1,
foo:function(){
console.log(this,this.x)
}
}
obj.foo()
此时我们的打印结果为{ x: 1, foo: [Function: foo] } 1
,也就是this
和this.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
函数,这时我们打开浏览器查看会发现:
它this指向的是windows
,且输出的是全局环境中的x,值为2
。这又是为什么呢,因为我们要时刻牢牢记住一个概念函数被谁调用this就指向谁,但是在我们这并没有发生任何函数调用,它只是作为一个普通的函数被执行了,所以它的this就会指向window
。
这时我们还有一个小小的知识点就是,如果在严格模式下执行这个函数,那么它的this就会由window
变为undefined
,这是因为this它既是JS中的goodpart
它也是badpart
,既然你都作为普通函数执行了,你就完全没必要指向任何对象呀。所以我们在<Script>
文件第一行加入:
"use strict"
然后再浏览器打开就会发现:
ok,接下来讲讲另一种this,就是通过new
关键字构造函数的方式执行,此时this
将会指向被实例化的对象,记住是实例化的对象本身,而不是变量名:
function Person(name) {
// 调用此函数的name被赋值为传进来的参数name
this.name = name;
console.log(this);
}
const person1 = new Person('Alice');
console.log(person1);
可以看到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>
这串代码会输出什么呢?答案是报错了,因为我们是执行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
,此时在浏览器中打开:
除了.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的功能对我们在写复杂的代码时有着很大的帮助,并且在面试时也常会考到,如果文章中有问题欢迎指出在评论区交流,希望能对你有帮助。