JavaScript 中的 this
普通函数的 this
指向
这里说的“普通函数”指箭头函数以外的函数。
在绝大多数情况下,函数的调用方式决定了 this
的值(运行时绑定)。
写出下面代码的执行结果:
// 当前位于全局作用域下
function testObject () {
alert(this)
}
testObject()
2
3
4
5
6
上题的答案:在chrome中会弹出 [object Window]
。
this
关键字是函数运行时自动生成的一个内部独享对象,只能在函数内部使用,总指向调用它的对象。
根据不同的使用场合,this
有不同的值,主要分以下几种情况:
- 默认绑定。
- 隐式绑定。
new
绑定。- 显示绑定。
默认绑定
全局环境中定义 person
函数,内部使用 this
关键字。
var name = 'Jenny';
function person() {
return this.name;
}
// Jenny
console.log(person());
2
3
4
5
6
7
上述代码输出 Jenny
,原因是调用函数的对象在游览器中为 window
,因此 this
指向 window
,所以输出 Jenny
。
注意:
严格模式下,不能将全局对象用于默认绑定,this
会绑定到 undefined
,只有函数运行在非严格模式下,默认绑定才能绑定到全局对象。
隐式绑定
函数还可以作为某个对象的方法调用,这时 this
就指这个上级对象。
function test() {
console.log(this.x);
}
const obj = {};
obj.x = 1;
obj.m = test;
obj.m(); // 1
2
3
4
5
6
7
8
9
下面这段代码中包含多级对象,注意 this
指向的只是它上一级的对象 b
(由于 b
内部并没有属性 a
的定义,所以输出 undefined
。)。
const o = {
a: 10,
b: {
fn: function() {
console.log(this.a); // undefined
}
}
}
o.b.fn();
2
3
4
5
6
7
8
9
再举一种特殊情况:
const o = {
a: 10,
b: {
a: 12,
fn: function() {
console.log(this.a); // undefined
console.log(this); // window
}
}
}
const j = o.b.fn;
j();
2
3
4
5
6
7
8
9
10
11
12
在上面的例子中,this
指向的是 window
,这里的大家需要记住,this
永远指向的是最后调用它的对象,虽然fn
是对象 b
的方法,但是 fn
赋值给 j
时并没有执行,所以最终指向 window
。
new
绑定
通过构建函数 new
关键字生成一个实例对象时,this
指向这个实例对象。
function Test() {
this.x = 1;
}
const obj = new Test();
obj.x // 1
2
3
4
5
6
上述代码之所以会输出 1
,是因为 new
关键字改变了 this
的指向。
这里再列举一些特殊情况:
new
过程遇到 return
一个对象(不包括 null
),此时 this
指向返回的对象:
function fn() {
this.user = 'xxx';
return {};
}
const a = new fn();
console.log(a.user); // undefined
2
3
4
5
6
如果 return
一个基础类型的值(包括 null
),则 this
指向实例对象:
function fn() {
this.user = 'xxx';
return 1;
}
const a = new fn;
console.log(a.user); // xxx
2
3
4
5
6
注意的是 null
虽然也是对象,但是此时 this
仍然指向实例对象:
function fn() {
this.user = 'xxx';
return null;
}
const a = new fn;
console.log(a.user); // xxx
2
3
4
5
6
显示修改
apply
、call
、bind
是函数的几个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时 this
指的就是这第一个参数。
const x = 0;
function test() {
console.log(this.x);
}
const obj = {};
obj.x = 1;
obj.m = test;
obj.m.apply(obj) // 1
2
3
4
5
6
7
8
9
匿名函数的 this
匿名函数里的 this
指向 window
。
// 等价于 `window.name = 'The Window'`
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function() {
return function() {
return this.name;
};
}
};
// 输出为 `The Window`
alert(object.getNameFunc()());
2
3
4
5
6
7
8
9
10
11
12
箭头函数的 this
指向
大部分情况下,this
总是指向调用该函数的对象。但对箭头函数而言却不是这样的,箭头函数里的 this
是根据外层(函数或者全局)作用域(词法作用域)来决定的。
箭头函数体内的 this
对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。
下面是普通函数的列子:
// 其实是window.name = 'window'
var name = 'window';
var A = {
name: 'A',
sayHello: function() {
console.log(this.name)
}
}
// 输出 `A`
A.sayHello();
var B = {
name: 'B'
}
// 输出 `B`
A.sayHello.call(B);
/**
* 不传参数指向全局 `window` 对象,
* 输出 `window.name` 也就是 `'window'`
*/
A.sayHello.call();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
从上面可以看到,sayHello
这个方法是定义在 A
对象中的,但是当我们使用 call
方法,把其指向 B
对象后,最后输出了 B
;可以得出,sayHello
的 this
只跟使用时的调用对象有关。
改造一下:
var name = 'window';
var A = {
name: 'A',
sayHello: () => {
console.log(this.name)
}
}
// 还是以为输出 `A`? 错啦,其实输出的是 `window`
A.sayHello();
2
3
4
5
6
7
8
9
10
11
我相信在这里,大部分同学都会出错,以为 sayHello
是绑定在 A
上的,但其实它绑定在 window
上的,那到底是为什么呢?
一开始,我重点标注了该函数所在的作用域指向的对象,作用域是指函数内部,这里的箭头函数(也就是 sayHello
)所在的作用域其实是最外层的 js 环境,因为没有其他函数包裹;然后最外层的 js 环境指向的对象是 window
对象,所以这里的 this
指向的是 window
对象。
那如何改造成永远绑定A呢:
var name = 'window';
var A = {
name: 'A',
sayHello: function() {
var s = () => console.log(this.name)
// 返回箭头函数 `s`
return s
}
}
var sayHello = A.sayHello();
// 输出 `A`
sayHello();
var B = {
name: 'B'
}
// 输出 `A`
sayHello.call(B);
// 输出 `A`
sayHello.call();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
OK,这样就做到了永远指向 A
对象了,我们再根据该函数所在的作用域指向的对象来分析一下:
- 该函数所在的作用域:箭头函数
s
所在的作用域是sayHello
,因为sayHello
是一个函数。 - 作用域指向的对象:
A.sayHello
指向的对象是A
。
最后是使用箭头函数其他几点需要注意的地方
- 不可以当作构造函数,也就是说,不可以使用
new
命令,否则会抛出一个错误。 - 不可以使用
arguments
对象,该对象在函数体内不存在。可以用...rest
入参代替((...rest) => { console.log(rest); }
)。 - 不可以使用
yield
命令,因此箭头函数不能用作 生成器(Generator)函数。
再来看一个例子:
var fullname = 'a'
var obj = {
fullname: 'b',
prop: {
fullname: 'c',
getFullname: () => {
return this.fullname;
}
}
}
// 打印 'a'
console.log(obj.prop.getFullname())
var test = obj.prop.getFullname
// 打印 'a'
console.log(test())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var fullname = 'a'
function hello () {
var fullname = 'd'
var obj = {
fullname: 'b',
prop: {
fullname: 'c',
getFullname: () => {
return this.fullname;
}
}
}
// 打印 'a'
console.log(obj.prop.getFullname())
var test = obj.prop.getFullname
// 打印 'a'
console.log(test())
}
hello()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var fullname = 'a'
function hello () {
var fullname = 'd'
var obj = {
fullname: 'b',
prop: {
fullname: 'c',
getFullname: () => {
return this.fullname;
}
}
}
// 打印 undefined
console.log(obj.prop.getFullname())
var test = obj.prop.getFullname
// 打印 undefined
console.log(test())
}
const d = new hello()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var fullname = 'a'
function hello () {
this.fullname = 'f'
var fullname = 'd'
var obj = {
fullname: 'b',
prop: {
fullname: 'c',
getFullname: () => {
return this.fullname;
}
}
}
// 打印 'f'
console.log(obj.prop.getFullname())
var test = obj.prop.getFullname
// 打印 'f'
console.log(test())
}
const d = new hello()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
看一个普通函数的例子:
const obj = {
count: 10,
doSomethingLater() {
setTimeout(function () {
// 这是一个匿名函数,是在 window 作用域下执行的
this.count++;
console.log(this.count);
}, 300);
},
};
// 打印 `NaN`,因为 window 对象上没有 `count` 属性
obj.doSomethingLater();
2
3
4
5
6
7
8
9
10
11
12
13
如果改成箭头函数:
const obj = {
count: 10,
doSomethingLater() {
// 该方法将 `this` 绑定到 `obj` 上下文中
setTimeout(() => {
/**
* 由于箭头函数内部不会自己绑定 `this`,
* `setTimeout` 函数也没有创建 `this` 绑定,
* 所以外部的 `obj` 上下文会被用作 `this`
*/
this.count++;
console.log(this.count);
}, 300);
},
};
// 打印 `11`
obj.doSomethingLater();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 参考: