深入理解 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。
 
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号