最新更新时间:2019.08.16
最近和小伙伴在网上看到了很多关于 this 指向的很有趣的面试题,在互相讨论中也对 this 的指向有了更深刻的理解,因此决定写下本文作为记录。
本文中会有较多的示例,以此来巩固对于不同情况下 this 指向的掌握。
前言:
this指向 只与 执行环境 有关,与 声明环境 无关。
1. this 指向调用该函数的对象
函数调用时,在没有被 new、call、bind、apply等影响的情况下,
哪个对象调用了函数,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(); // ?
------ 解读 ------
此时 fn 被 getA 对象使用.调用了,因此 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. 用 call、apply、bind 替换 this 的绑定
call、apply、bind 都可以用于改变函数内部 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 在执行时若是传入了原函数所需要的参数,则之后调用该函数时无论用什么办法传参,都无法改变默认参数了。