call(), apply() 和 bind()

call() 和 apply() 这两个方法的作用完全相同,都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。它们的唯一区别在于,apply() 接受一个参数数组或 arguments 对象,而 call() 只能依次传入参数。call() 和 apply() 能够扩充函数赖以运行的作用域,这样做的最大好处是对象不需要与方法有任何耦合关系。

bind() 的作用也是改变方法的 this 指向,但与 call() 和 apply() 的区别在于,bind() 将返回一个新的函数,新函数的 this 值将绑定到 bind() 的参数上。

bind() 与 call() 和 apply() 的区别在于,call() 和 apply() 在改变 this 指向后将立即执行函数,而 bind() 则只是返回一个新的函数而不立即执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var bob = {
name: 'Bob',
sayInfo: function (age, school) {
return `Name: ${this.name}. Age: ${age} years old. Studying at ${school}.`
}
};
var violet = {
name: 'Violet',
};

console.log(bob.sayInfo(18, 'Tsinghua University'));
// Name: Bob. Age: 18 years old. Studying at Tsinghua University.
console.log(bob.sayInfo.call(violet, 22, 'University of Edinburgh'));
// Name: Violet. Age: 22 years old. Studying at University of Edinburgh.
console.log(bob.sayInfo.apply(violet, [22, 'University of Edinburgh']));
// Name: Violet. Age: 22 years old. Studying at University of Edinburgh.

var violetSayInfo = bob.sayInfo.bind(violet);
console.log(violetSayInfo(19, 'Stanford University'));
// Name: Violet. Age: 19 years old. Studying at Stanford University.

// 由于 bind() 返回一个新的函数,因此可以通过这样的方式传递参数并直接执行
console.log(bob.sayInfo.bind(violet)(19, 'Stanford University'));
// Name: Violet. Age: 19 years old. Studying at Stanford University.

箭头函数和普通函数的区别

由于任意普通函数可以当作构造函数使用,因此当函数被作为构造函数时,this 将指向由此函数构造的新的对象。如果普通函数是被对象调用,则函数内的 this 将指向对象。

而箭头函数的 this 指向与普通函数有很大区别。在 阮一峰的《ECMAScript 6 入门: 函数的扩展》 这一节,对箭头函数的描述中有这样一句话:

函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。

有一种更简单的理解: 所有的箭头函数都没有自己的 this,都指向外层,层层递进直至找到距离对象最近的作用域。下面的例子中,person 属于 Window 作用域,因此全部使用箭头函数时 this 将层层向上直至指向 Window。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
var person = {
firstName: 'Peter',
lastName: 'Parker',
sayInfo: function () {
const buildFullName = function () {
return this.firstName + ' ' + this.lastName;
}
return `My name is ${buildFullName()}`;
},
sayInfo2: function () {
const buildFullName = () => {
return this.firstName + ' ' + this.lastName;
}
return `My name is ${buildFullName()}`;
},
sayInfo3: () => {
const buildFullName = () => {
return this.firstName + ' ' + this.lastName;
}
return `My name is ${buildFullName()}`;
},
sayInfo4: function () {
const buildFullName = function () {
return this.firstName + ' ' + this.lastName;
}
return `My name is ${buildFullName.call(this)}`;
}
};

console.log(person.sayInfo());
// My name is undefined undefined
// 此时 buildFullName 函数内的 this 指向 Window

console.log(person.sayInfo2());
// My name is Peter Parker
// buildFullName 的 this 指向外层作用域,由于 sayInfo2 使用了普通函数,因此 buildFullName 的 this 实际上是 sayInfo2 的 this

console.log(person.sayInfo3());
// My name is undefined undefined
// buildFullName 内的 this 等于 sayInfo3,sayInfo3 的 this 又指向 window,因此此处 buildFullName 中的 this 仍将指向 window

console.log(person.sayInfo4());
// My name is Peter Parker
// 使用 call 方法将 buildFullName 的 this 绑定为 sayInfo4 的作用域,因此 buildFullName 内能访问到 person 内的属性

var firstName = 'Bob', lastName = 'Dylan';
console.log(person.sayInfo());
console.log(person.sayInfo3());
// My name is Bob Dylan
// 如果在 window 作用域上挂载两个全局变量,那么指向 window 的 this 就都可以访问到了

Class 中的 this

在 ES6 的 Class 语法中,this 的指向非常得清晰。方法中的 this 永远指向类的实例,而类方法内部再次定义的普通函数的 this 为 undefined。

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
26
27
28
29
30
31
32
33
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

sayInfo() {
return `My name is ${this.firstName} ${this.lastName}`;
}

sayInfo2() {
const buildFullName = function () {
// 此处的 this 为 undefined
return this.firstName + ' ' + this.lastName;
}
return `My name is ${buildFullName()}`;
}

sayInfo3() {
const buildFullName = function () {
return this.firstName + ' ' + this.lastName;
}
return `My name is ${buildFullName.call(this)}`;
}
}

const peter = new Person('Peter', 'Parker');
console.log(peter.sayInfo())
// My name is Peter Parker
console.log(peter.sayInfo2())
// Uncaught TypeError: Cannot read property 'firstName' of undefined
console.log(peter.sayInfo3())
// My name is Peter Parker

推荐阅读