有关this的文章也是看了一篇有一篇,总觉得会了,但是时间久了就又忘了,这次彻底走一遍,后面就按照这个看了。

首先,我们来回顾一下执行上下文的内容
全方位解读this - 图1

执行上下文的创建阶段,分别生成变量对象,建立作用域链和确定this的指向。而执行上下文的创建是在函数调用的时候,所以:
this的指向,是在函数被调用的时候确定的,也是说执行上下文被创建的时候确定的。

所以同一个函数,调用方式的不同,就会导致this指向不一样的对象。

  1. var a = 10
  2. var obj = {
  3. a: 20
  4. }
  5. function fn() {
  6. console.log(this.a)
  7. }
  8. fn() //10
  9. fn.call(obj) // 20

除此之外,this被确定后就无法再次修改,所以,如果我们在fn函数中试图去更改this的指向,就会报错

全局对象中的this

在浏览器中,this就相当于window,node中就是global对象,全局对象中的this也很简单明了

  1. this.a1 = 10
  2. var a2 = 20
  3. a3 = 30
  4. //三种赋值操作,最终对象都是在全局对象中,这里的this也都指向全局对象
  5. console.log(a1)
  6. console.log(a2)
  7. console.log(a3)

函数中的this

我们以及知道稍微一点点的改变,就会让this的指向发生天翻地覆的变化,所以在刷面试题的时候,你有可能见到一下,看上去没什么区别的代码,但是结果却天差地别,下面我们就先来看几个this相关的变态代码

  1. var a = 20
  2. function fn() {
  3. console.log(this.a)
  4. }
  5. fn()
  1. var a = 20
  2. function fn() {
  3. function foo() {
  4. console.log(this.a)
  5. }
  6. foo()
  7. }
  8. fn()
  1. var a = 20
  2. var obj = {
  3. a: 10,
  4. c: this.a + 20,
  5. fn: function() {
  6. return this.a
  7. }
  8. }
  9. console.log(obj.c)
  10. console.log(obj.fn())

首先,我们先把判断this指向的方法写出来,再根据这个方法来看上面的代码:
在一个函数上下文中,this由调用者提供,由调用函数的放式来决定。如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。如果函数是独立调用,那么该函数内部的this指向undefined。非严格模式下,this指向undefined就会自动指向全局对象。

根据上面这句话,第一个例子就很简单,fn独立调用,指向全局对象,输出20
第二个例子稍微有些迷惑,但其实所有的迷惑都只是迷惑,foo的调用仍然是独立调用,foo中的this仍然是指向全局。
最后一个例子,fn的调用很简单,被obj所拥有,所以fn中的this指向obj对象,而访问c中的this.a只是在一个{}中,并没有独立的作用域,所以这里的this仍然处于全局作用域之中。

这里我们可以对第三个例子进行一点小小的修改:

  1. 'use strict'
  2. var a = 20
  3. function foo() {
  4. var a = 1
  5. var obj = {
  6. a: 10,
  7. c: this.a + 20,
  8. fn: function() {
  9. return this.a
  10. }
  11. }
  12. return obj.c
  13. }
  14. console.log(foo())
  15. console.log(window.foo())

一堆无用的代码,让这段代码变得不忍直视,但是对于this的考察,这确实是一段好代码,无视掉那些没有的迷惑代码,foo()的调用是独立调用,所以this指向undefined,但是这里是严格模式,所以就会报错。下面利用window.foo()this指向了window,所以this.a = 20,也就输出了40.
并没有想象中的那么复杂,看似复杂的地方其实都只是在迷惑你。只需要确定函数的调用是独立调用还是被某一个对象拥有就好。
下面再来几个例子,加深一下是否独立调用的理解。

  1. var a = 20;
  2. var foo = {
  3. a: 10,
  4. getA: function () {
  5. return this.a;
  6. }
  7. }
  8. console.log(foo.getA()); // 10
  9. var test = foo.getA;
  10. console.log(test()); // 20
  1. var a = 20;
  2. function getA() {
  3. return this.a;
  4. }
  5. var foo = {
  6. a: 10,
  7. getA: getA
  8. }
  9. console.log(foo.getA()); // 10
  1. function foo() {
  2. console.log(this.a)
  3. }
  4. function active(fn) {
  5. fn(); // 真实调用者,为独立调用
  6. }
  7. var a = 20;
  8. var obj = {
  9. a: 10,
  10. getA: foo
  11. }
  12. active(obj.getA);

我相信看完这些,你的逻辑应该已经很清晰了。

使用call,apply显式指定this

JavaScript内部提供一种机制,让我们可以修改this的指向,他们就是使用call和apply。这两个方法功能一样,只是参数不同,第一个参数都是作为this将要指向的对象。后面的参数是给将要执行的函数传递的参数,apply传递的是一个数组,call以一个一个值的形式传递。这里我之前一直分不清,后来想到一个方法,apply是以a开头,所以传递的是一个array。

  1. function fn(num1, num2) {
  2. console.log(this.a + num1 + num2)
  3. }
  4. var obj = {
  5. a: 20
  6. }
  7. fn.call(obj, 10, 20)
  8. fn.apply(obj, [10, 20])

有了这两个方法的存在,我们的this的使用就更灵活了。

根据需要灵活修改this

  1. var foo = {
  2. name: 'joker',
  3. showName: function () {
  4. console.log(this.name);
  5. }
  6. }
  7. var bar = {
  8. name: 'rose'
  9. }
  10. foo.showName.call(bar);

实现继承

  1. // 定义父级的构造函数
  2. var Person = function (name, age) {
  3. this.name = name;
  4. this.age = age;
  5. this.gender = ['man', 'woman'];
  6. }
  7. // 定义子类的构造函数
  8. var Student = function (name, age, high) {
  9. // use call
  10. Person.call(this, name, age);
  11. this.high = high;
  12. }
  13. Student.prototype.message = function () {
  14. console.log('name:' + this.name + ', age:' + this.age + ', high:' + this.high + ', gender:' + this.gender[0] + ';');
  15. }
  16. new Student('xiaom', 12, '150cm').message();
  17. // result
  18. // ----------
  19. // name:xiaom, age:12, high:150cm, gender:man;

Student中,利用了call,引用了Person中的内容,但是this的指向还是在student中,所以相当于是复制了Person的内容到Student中,这就相当于是实现了继承。
而对于原型上面的方法就更简单了,调用原型上的方法是通过new出来的Student,所以原型方法中的this指向new放回的新对象。

this丢失

  1. var obj = {
  2. a: 20,
  3. getA: function() {
  4. setTimeout(function () {
  5. console.log(this.a)
  6. }, 1000)
  7. }
  8. }
  9. obj.getA()

上面的代码根据我们之前的经验,getA()被obj拥有,所以输出20.但结果并不是。因为setTimeout匿名函数的存在,匿名函数内部的this指向了全局,这里的a并不是我们想要的20
解决办法有很多,第一个可以想到的就是在getA中定义一个变量self保存this,在匿名函数中通过self来访问,这也是我们之前经常用到的。另外还有一个方法可以实现,那就是bind方法。

bind

  1. var obj = {
  2. a: 20,
  3. getA: function () {
  4. setTimeout(function () {
  5. console.log(this.a)
  6. }.bind(this), 1000)
  7. }
  8. }