this指向——JavaScript

313 阅读6分钟

最新更新时间:2019.08.16


最近和小伙伴在网上看到了很多关于 this 指向的很有趣的面试题,在互相讨论中也对 this 的指向有了更深刻的理解,因此决定写下本文作为记录。

本文中会有较多的示例,以此来巩固对于不同情况下 this 指向的掌握。

前言:this指向 只与 执行环境 有关,与 声明环境 无关。

1. this 指向调用该函数的对象

函数调用时,在没有被 newcallbindapply等影响的情况下,

哪个对象调用了函数,this 就指向哪个对象。

哪个对象调用了函数,this 就指向哪个对象。

哪个对象调用了函数,this 就指向哪个对象。

1.1 直接调用

示例1

function fn() {
  var a = 1;
  console.log(this); // window,严格模式下是undefined
  console.log(this.a); // 10
}
var a = 10;
fn();

------ 解读 ------

函数 直接调用 且不带任何修饰时,等价于 window.fn(), 由于 fn 是被 window 调用,所以 fn 内部的 this 指向了 window

在 严格模式 下,被 直接调用 的函数的 this 会指向 undefined

示例2

var a = 5;

var obj = {
  a: 1,
  objFn: function() {
    console.log(this);
    console.log(this.a);
  },
};

var fn = obj.objFn;

fn(); // this=?  this.a=?

------ 解读 ------

记住那句话,哪个对象调用了函数,this 就指向哪个对象。

我们只需要看 fn 在最终调用时,前面是没有对象的,因此等价于 window.fn()

所以执行的结果为: this --> window this.a --> 5

在 严格模式 下,被 直接调用 的函数的 this 会指向 undefined。 此时this.a就会报错: Uncaught TypeError: Cannot read property 'a' of undefined

1.2 对象调用

示例1

var getA = {
  a: 1,
  fn: function() {
    console.log(this.a);
  },
};
getA.fn(); // ?

------ 解读 ------

此时 fngetA 对象使用.调用了,因此 fn 中的 this 指向了 getA。 因此最后打印的结果是 1

示例2

var getA = {
  a: 1,
  fn: function() {
    console.log(this.a);
  },
};
window.getA.fn(); // ?

------ 解读 ------

根据我们的方法,首先要做的就是找出谁调用了 fn 这个函数。

通过代码可以发现,此时 fn 是被 getA 对象使用.调用。所以 fn 中的 this 还是指向 getA

因此最后打印的结果是 1

示例3

var getA = {
  a: 1,
  b: {
    a: 5,
    fn: function() {
      console.log(this.a);
    },
  },
};
getA.b.fn(); // ?

------ 解读 ------

这道题跟上一道一样,可以看出最终调用 fn 的对象是 b。 所以 fn 中的 this 指向 b

因此最后打印的结果是 5

2. 用 callapplybind 替换 this 的绑定

callapplybind 都可以用于改变函数内部 this 的指向。 这里仅说明三个函数对于 this 的影响,不做深入探讨。 我们先举一个简单的例子:

var obj1 = {
  name: '小红',
  intro: function(age, from) {
    console.log('姓名:' + this.name + '年龄:' + age + '出生地:' + from);
  },
};

var obj2 = {
  name: '隔壁老王',
};

obj1.intro(15, '北京'); // 姓名:小红  年龄:15  出生地:北京

/* 
intro方法被obj1调用,因此this指向obj1,得到this.name='小红'
传入参数 -- 年龄 和 出生地
*/

这时候 隔壁老王 有个需求:

隔壁老王 要去相亲,他也想要自我介绍。

他需要调用 intro 函数,把里面的 this.name 指向自己的名字,并把 年龄出生地 参数传给 intro 函数。

最终打印出:姓名:隔壁老王 年龄:30 出生地:上海

2.1 call

fn.call(obj, 参数1, 参数2, ..., 参数x);

参数 描述
obj 要调用 fn函数 的对象
参数1 fn函数 所需要的第一个参数
参数2 fn函数 所需要的第二个参数
... ...
参数x fn函数 所需要的第x个参数

call第一个参数就是指定原函数函数中的 this 要指向的那个 对象

从第二个参数开始,就是原函数原本所需要的参数了。

示例1

var obj1 = {
  name: '小红',
  intro: function(age, from) {
    console.log('姓名:' + this.name + '年龄:' + age + '出生地:' + from);
  },
};

var obj2 = {
  name: '隔壁老王',
};

obj1.intro.call(obj2, 30, '上海'); // 姓名:隔壁老王  年龄:30  出生地:上海

------ 解读 ------

这里可以看出,本来 intro 内的 this 会指向 obj1

但是我们使用了 call ,改变了 intro 内部的 this 指向。

第1个参数传入要调用 intro 的对象 obj2, 第2、3个参数传入了 intro 所需的年龄、出生地参数。

因此最终成功打印出了:姓名:隔壁老王 年龄:30 出生地:上海

2.2 apply

fn.apply(obj, [ 参数1, 参数2, ..., 参数x ]);

参数 描述
obj 要调用 fn函数 的对象
[ 参数1, 参数2, ..., 参数x ] 以数组的形式,传入fn函数所需要的参数列表

apply 只接收两个参数。

apply第一个参数call 的第一个参数一样,传入原函数中的 this 需要指向的对象。

apply第二个参数必须是数组,这个数组代表原函数的参数列表。

示例1

var obj1 = {
  name: '小红',
  intro: function(age, from) {
    console.log('姓名:' + this.name + '年龄:' + age + '出生地:' + from);
  },
};

var obj2 = {
  name: '隔壁老王',
};

obj1.intro.apply(obj2, [30, '上海']); // 姓名:隔壁老王  年龄:30  出生地:上海

------ 解读 ------

使用方法几乎与 call 一样,区别仅在于如何给原函数传参。

obj1.intro.call(obj2, 30, '上海');

obj1.intro.apply(obj2, [30, '上海']);

打印结果一模一样。

2.3 bind

var foo = fn.bind(obj, 参数1, 参数2, ..., 参数x);

参数 描述
obj 调用绑定函数时作为 this 参数传递给目标函数的值
参数1 非必填,当 目标函数 被调用时,预先添加到 绑定函数 的参数列表中的 参数1
参数2 非必填,当 目标函数 被调用时,预先添加到 绑定函数 的参数列表中的 参数2
... ...
参数x 非必填,当 目标函数 被调用时,预先添加到 绑定函数 的参数列表中的 参数3
返回值 描述
foo 返回一个原函数的拷贝,并拥有指定的this值初始参数

bind只有一个函数,且不会立刻执行,只是将一个值绑定到函数的this上,并将绑定好的函数返回

乍一看之下好像跟 call有点类似,但是 call 会直接调用原函数。

bind 不会直接调用,他只是将函数中的 this 指向绑定后,拷贝一个新的函数并返回。

bind 绑定时传给原函数参数是一个默认值,之后无法被新的参数覆盖。

因此 bind 绑定一个函数可以有两种写法,可以根据需求来选择。

示例1

var obj1 = {
  name: '小红',
  intro: function(age, from) {
    console.log('姓名:' + this.name + '年龄:' + age + '出生地:' + from);
  },
};

var obj2 = {
  name: '隔壁老王',
};

var myIntro = obj1.intro.bind(obj2);

myIntro(30, '上海'); // 姓名:隔壁老王  年龄:30  出生地:上海
myIntro(40, '深圳'); // 姓名:隔壁老王  年龄:40  出生地:深圳

------ 解读 ------

bind 不会立即调用函数,而是返回一个新的函数,由 myIntro 接收。

bind 在执行时若是没有传入原函数所需要的参数,则可以在新函数调用时传入新的参数并调用。

示例2

var obj1 = {
  name: '小红',
  intro: function(age, from) {
    console.log('姓名:' + this.name + '年龄:' + age + '出生地:' + from);
  },
};

var obj2 = {
  name: '隔壁老王',
};

var myIntro = obj1.intro.bind(obj2, 30, '上海');

myIntro(); // 姓名:隔壁老王  年龄:30  出生地:上海
myIntro(40, '深圳'); // 姓名:隔壁老王  年龄:30  出生地:上海

------ 解读 ------

bind 在执行时若是传入了原函数所需要的参数,则之后调用该函数时无论用什么办法传参,都无法改变默认参数了。