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