深入理解JavaScript
的this
指针
hello everybody,我又来了。上一篇文章中,我们讲解了JavaScript
的原型,有兴趣的同学可以通过传送门进入查看,今天我们来聊聊JavaScript
中的this
。一起探究下这个比较难理解的问题。话不多说,让我们进入正题吧👇👇👇
1.this
指针具体是什么
首先开始第一个问题,想要了解this
,那就得先知道它是什么东西。先来看一个例子。
this is a little dog. ---> 这是一只小狗
这句话可以让我们很清楚的明白this
所指的是一只小狗。
灵光一闪,咦,好像明白了点儿什么。
不急,接着来看,
this.name = "二狗子"
通过这个句子,我们能很快的知道,这只小狗的名字叫二狗子
。现在想象一下,将你的手指指向其他的小狗、小猫……
this is a cat.
this.name = "球球" // 这是一个猫,它叫球球
this is a other little dog.
this.name = "狗剩子" // 这是另一只小狗,他的名字叫 狗剩子
// 作者内心是崩溃的,身为一个英语渣渣,这几句英语解释,简直要了老命。
看到这里的你是不是会感觉豁然开朗,我们的手指指向谁,this
代表的就是谁。
哇哦,感觉打开了新世界的大门,this is so easy
。
重点来了!!!
this
到底是什么呢?
它是一个代指,我们经常说在JavaScript
中万物皆对象,那么,this
就是代指这一个个对象,最重要的是看你(this
)的手指指向谁。
是不是需要巩固一下了?
this is lucy.
this is lilei.
this is liming.
this is me.
this is my brother.
this is a people.
…………………………
抛开那些烦人的英文吧,代码才是我的追求……
2.this
指针的具体指向
想知道this
具体代指的是哪个对象,最关键的还是要弄懂它的指向。
2.1 无闭包的函数
闭包的内容会在后续章节中讲解,敬请期待。
知识点1:
一般情况下,this
代指的是调用它的那个对象(闭包和箭头函数除外,稍后讲解)
怎么理解这句话呢?小栗子上场
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"></head><body></body></html>
<script>
function getName () {
console.log(this === window) // true
}
// 调用
getName()
</script>
这里定义了一个getName
函数。调用的时候并没有指定是谁调用,那,它的this指向到了哪儿呢?
知识点2:
全局的方法和属性都是window
对象的方法和属性,也就是说,这里的调用其实是 window.getName()
,是 window
调用的,也就是它的this
指向是window
没理解?好吧,再看一个栗子
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"></head><body></body></html>
<script>
let obj = {
getName: getName
}
function getName () {
console.log(this === window) // false
console.log(this === obj) // true
}
obj.getName()
</script>
定义一个对象,将getName
当做这个对象的方法,最后通过obj.getName()
来执行,你会惊喜的发现,this已经指向了obj
。原因就是:调用函数的对象是obj
2.2 有闭包的函数
众所周知,计时器也是闭包的一种,为了不让大家对代码有不理解的地方,这里我们使用计时器实现闭包。
还是熟悉的配方,还是熟悉的味道,还是熟悉的栗子。
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"></head><body></body></html>
<script>
let obj = {
getName: getName
}
function getName () {
setTimeout(function xiaosan() {
console.log(this === obj) // false
}, 0)
}
obj.getName()
</script>
纳尼!!不是obj
调用的吗?不是this
会指向obj
吗?究竟是谁,是谁让原本忠贞的爱情出现了裂痕?
别急别急,我们来分析一下,obj
调动了getName
函数。但是计时器中的xiaosan
函数并不是由obj
调用的,所以this
肯定不会指向obj
。那么,xiaosan
的this
指向了谁呢?我们将setTimeout
改写为下面这种写法
setTimeout(function xiaosan() {
console.log(this) // window
}, 0)
哦~原来window
就是那个第三者。终于抓到你了。
见证奇迹的时刻,让我们为错位的爱情复位吧!
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"></head><body></body></html>
<script>
let obj = {
getName: getName
}
function getName () {
setTimeout(function xiaosan() {
console.log(this === obj) // true
}.bind(this), 0)
}
obj.getName()
</script>
长舒一口气,终于圆满了。
知识点3:
闭包中的this
指向为window
。可以使用箭头函数、call、apply、bind
等手法改变this
指向。
2.3 箭头函数
首先介绍下箭头函数。官方文档 说到,箭头函数有以下几个特性:
- 没有单独的
this
- 不能通过
call、apply、bind
方法改变this
指向(因为没有😂) - 不绑定
arguments
- 不能使用
new
操作符 (因为没有this
😂) - 没有
prototype
(具体介绍请看我上一篇文章) - 不能作为函数生成器,不能在箭头函数中使用
yield
- 除非是箭头函数内嵌套了一个普通函数,将
yield
用在普通函数中
- 除非是箭头函数内嵌套了一个普通函数,将
看到这里突然发现,箭头函数实在是太惨了,这么多不能干的。既然这么多限制,那还用箭头函数干什么?
好处很多,这里我们只介绍与本节课相关的,箭头函数可以“自动绑定”this
举个栗子看下:
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"></head><body></body></html>
<script>
let obj = {
getName: () => {
console.log(this === obj) // false
},
getAge: function () {
console.log(this === obj) // true
}
}
obj.getName()
obj.getAge()
</script>
同样的两个方法调用,第一个返回的竟然是false
。这是为什么?明明我就是使用obj
调用的。别急,我们来打印下this
,看它指向了哪儿。
getName: () => {
console.log(this) // window
},
知识点4:
箭头函数不会创建自己的this
,只会从作用域链的上一层继承this
这里的作用域上一层为window
对象,所以,this
指向了window
。
我们再来看看闭包中的箭头函数
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"></head><body></body></html>
<script>
let obj = {
getName: getName
}
function getName () {
setTimeout(() => {
console.log(this === obj) // true
}, 0)
}
obj.getName()
</script>
刚才我们这个打印是false
,现在我们把xiaosan
函数改成箭头函数,惊喜的发现,this
指向了obj
。是因为getName
函数的上一层this
是obj
,继承了过来。
3.修改this指针的指向
手动改变this
指针,可以帮助我们更明确的知道自己要执行的方法。改变指针的方法有三种。
- call
- apply
- bind
3.1 call、apply
这两兄弟性格相近,那就放到一块儿来介绍吧。
使用call()
或apply()
扩充作用域最大的好处就是, 对象不需要与方法有任何耦合关系,
它们的作用就是在函数的体内设置this
的值。也就是我们说的改变this
的指向。怎么改变的呢?
😏小栗子又要出场了。
call 的小栗子
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"></head><body></body></html>
<script>
let obj = {
name: '一个对象',
getName: function (a,b,c) {
globalGetName.call(this,a,b,c)
}
}
function globalGetName (a,b,c) {
console.log(this.name,a,b,c) // 一个对象 xiaosan xiaosi xiaowu
}
obj.getName("xiaosan", "xiaosi", "xiaowu")
</script>
知识点5:
call
的特点
-
将
this
绑定到当前环境中, -
参数逐个传入。
apply 的小栗子
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"></head><body></body></html>
<script>
let obj = {
name: '一个对象',
getName: function (a,b,c) {
globalGetName.apply(this,[a,b,c])
}
}
function globalGetName (a,b,c) {
console.log(this.name,a,b,c) // 一个对象 xiaosan xiaosi xiaowu
}
obj.getName("xiaosan", "xiaosi", "xiaowu")
</script>
知识点6:
apply
的特点
- 将
this
绑定到当前环境中, - 传入一个参数列表,如
[a,b,c]
。
3.2 bind
在调用bind
方法之后,会将bind
中传入的第一个参数作为this
绑定到当前函数上。之后的参数会逐个传入到函数中。
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"></head><body></body></html>
<script>
let obj = {
name: '一个对象'
}
function getName (a) {
console.log(this.name, a)
}
getName.bind(obj, '111')() // 一个对象 111
</script>
可以看到,我们通过getName.bind
将obj
传入,作为getName
的this
。那么在执行getName
的时候,this
代指的就是obj
对象。那么this.name === obj.name
。将第二个参数通过入参的方式传入到getName
上。
知识点7:
bind
的特点
- 不会自动执行,通过
bind
绑定this
后,需要手动执行函数。如getName.bind(obj, '111')()
- 传参方式和
call
相同
首先,恭喜能看到这里的你,说明我的文章也不是这么让人厌烦。接下来我会通过一个面试题来具体讲解this
指针的变换。准备好了吗? Let’s go……
4.面试题说明
var foo = 'fooBar'
var obj = {
foo: "bar",
func: function () {
var self = this;
console.log(this.foo); // 1
console.log(self.foo); // 2
(function () {
console.log(this.foo); // 3
console.log(self.foo); // 4
}());
}
};
obj.func();
输出:
- bar
- bar
- fooBar
- Bar
解析:
-
当执行
obj.func
的时候,this
指向了obj
。此时1 和 2
都能正常的方位obj.foo
-
3
因为在一个闭包中,this
指向了window
,所以会找到全局的foo = ‘fooBar’
-
4
因为缓存了this
,所以self
代指的还是obj
这个对象,self.foo === obj.foo
。