Javascript是一门基于对象的动态语言,也就是说,所有东西都是对象,一个很典型的例子就是函数也被视为普通的对象。
其中this就是实现面向对象的一个非常重要的特性,但是 this在Javascript非常容易理解错,尤其是对于接触静态语言比较久的同学来说。而且 this又是面试中和实际项目中的重中之重,不得不单独拿出一篇文章来把它理解透透的。
上面说到this参数是面向对象Javascript编程的一个重要组成部分,代表函数调用相关联的对象,也称为函数上下文。我知道,你可能是个初学 JS 的同学,听不懂,没关系,不用担心,因为下面还有动画来更好的理解。
思维导图
1. 什么是 this?
什么是 this呢?上边我们说 this是一个对象,是个啥对象?咱们就来动手敲代码打印一下。我们最常见的 this是在一个函数中, JS 的函数调用有两种方式,一种是我们直接调用,另外一种就是通过 new 的方式来调用,我们通过两种方式来打印一下 this值是否相同?。
控制台输出如下:
吆喝?咦?虽然都是在函数中,咋打印出来的不一样呢?直接通过函数调用的方式打印出来的 this指向的是全局变量Window;通过new的方式调用的函数当做为构造函数,为了能够创建一个实例对对象,它的 this值指向生成的实例对象。
那我们通过上边的一顿乱操作,知道了 this是一个对象,但是我们不同的操作 this指向的对象是不相同的。写到这里,知道 this是个什么东西就可以了,下面我们再慢慢深入 this原理。
2. 如何判断 this?
既然我们知道 this 是什么东西了,但是怎么判断 this的值呢?也是我们上边没有解决的问题。this的指向有三种情况,只要理解了这三种情况,也不用死记硬背,判断 this如鱼得水,在面试中给面试官讲的滚瓜烂熟。
三种 this指向情况:
(1) 对象调用,this 指向该对象(前边谁调用 this 就指向谁)。
对于第一种情况,通过对象调用的方式,this指向谁?要想一探究竟,必须动手实践一下。小鹿,上代码,好嘞~。
控制台打印:
我们通过亲手测试,我们发现 this的指向就是 obj,所以我们总结归纳为谁调用了函数, this就指向谁,很简单吧,我们继续向下看。
(2) 直接调用的函数,this 指向的是全局 window 对象。
其实这也属于第一种情况,如果我们直接在全局函数调用了函数,其实是全局的对象 Window调用了函数,根据第一条我们得出的结论,谁调用的函数,this就指向谁,想必你已经知道了第二种情况 this指向的就是 Window。
(3) 通过 new 的方式,this 永远被绑定在新创建的对象上,任何方式都改变不了 this 的指向。
第三种方式刚才我们也测试过了,this指向的是指向生成的对象实例,很多好奇的小伙伴就会问小鹿,很好奇 new的内部实现,到底做了什么,其实没什么复杂的,不告诉你估计你也通过上边的两个结论可以得出。
我们从的到的结果进行反推,通过 new的方式 this 指向的是生成的对象实例,那我们猜测肯定内部让这个实例对象调用了函数,所以 this 才指向生成的对象实例。
真实的情况是这样子吗?确实是,我们 new的过程,其实在内部创建了一个空对象,然后将构造函数传入的参数和属性挂在了这个空对象上,然后返回了这个对象。还涉及该空对象到原型链的的挂载,想要具体了解,可以自己探究下,这里不多说了。
扩展:箭头函数的 this 指向谁?
我们都知道 ES6 之后,为了使用函数更加方便,在项目中我们会使用箭头函数,书写方式:
运行程序,控制台输出:
我们可以得出结论,this在箭头函数中失效了,因为这是由于箭头函数没有单独的 this值。箭头函数的 this与声明所在的上下文相同。也就是说调用箭头函数的时候,不会隐士的调用 this参数,而是从定义时的函数继承上下文。
有关箭头函数这一点,一定要注意,面试的时候也会经常给你刨坑哦!
3. 如何改变 this 的值?
我们对 this的指向已经把它翻了个底朝天,但是不要傲娇,需要自己找点有关 this 的大厂面试题去做,这样巩固一下加深理解。
this可以指向不同的对象,我们想要改变 this有没有办法呢?有的,改变 this的方法共有三种,我们具体看看这三种方法之间的实现和区别,也是面试重点哦!
(1) call 方法
call方法用来改变 this的指向,具体咱们先看实例:
控制台输出如下:
(2) apply 方法
我们再来看apply方法,同样举个例子:
控制台输出如下:
我们发现输出的值相同,我们先不比较两者的区别是什么,我们继续往下看bind函数。
(3) bind 方法
我们在用 bind函数举个例子:
控制台输出如下:
(4) call、apply、bind 三者的区别是什么?
我们对于三者都进行举例运行了,我们开始做总结归纳,我们先找找三者的共同点是什么?
共同点:
- 都能改变 this 指向,第一个传递的参数都是 this 指向的对象。
- 三者都采用的后续传参的形式。
三者相同点表面上都能看出来,功能都是一样的,但是对于不同点,就涉及到细节了,不知道你发现了没有?
不同点:
- call的传参是单个传递的,而 apply后续传递的参数是数组形式,而 bind没有规定,传递值和数组都可以。
- call和 apply函数的执行是直接执行的,而 bind函数会返回一个函数,然后我们想要调用的时候才会执行。
你可能会有疑惑,小鹿,难道你就是这样表面看出的吗?虽然我们表面可以看得出,那不妨我们自己手写一个 call、apply、和 bind的吧,看了源码的实现,你会觉得这三个函数也没有什么难的。因为涉及到时间问题,我们就不展开讲了,小鹿把代码贴到下边了,感兴趣的可以研究一下。
手写 call 函数:
手写 apply 函数:
手写 bind 函数:
PS:如果我们使用上边的方法改变箭头函数的 this 指针,会发生什么情况呢?能否对齐进行改变呢?
由于箭头函数没有自己的this指针,通过 call() 或 apply() 方法调用一个函数时,只能传递参数(不能绑定this),他们的第一个参数会被忽略。