JavaScrip 中的 this, bind, call & apply 简述
this 是一个比较特殊的东西,基本上可以理解成 this 的指向是就近调用的指向,因此 this 在 JS 中也是一个比较令人困惑的知识点。
之前绕过 this 的方法基本上采用 arrow function,因为 arrow function 不包含对 this, arguments, 或者 super 的绑定,因此使用 arrow function 时,this 的指向更容易判断一些,不过最近感觉也是时候细究一下 this, call, apply 和 bind 了。
this 的指向问题
上面说了,this 的指向采用的是就近调用原则,以下面的代码为例:
const person = {
name: 'John',
greet() {
console.log(this);
console.log('Hello ' + this.name);
},
};
正常情况下,greet 中的 this 指向应该指代的是 person,在下面这种调用的情况下是正确的:
person.greet();

这个情况下,greet 是通过 person 这个对象去调用的,因此 greet 绑定的环境是 person 对象本身。
不过换一种方式调用,就会有不同的结果:
const { greet } = person;
greet();
这时候的 greet 指向就变成了 global,或者在严格模式下变为 undefined:


这是因为 greet 被解构出来了,在这个时候调用它的是 global 对象下,换言之,这个时候的 this 就会自动绑定成 global 对象。
再换一种调用的方法:
const user = {
name: 'user',
};
user.greet = person.greet;
user.greet();
这个时候的 greet 因为是被 user 调用的,因此绑定的对象就是 user:

大多数情况下 this 的问题不是很大,不过有的时候用到 callback 时就会是一个比较麻烦的事情,如下面模拟一个挂载 callback,并且实现延后执行的情况:
const example = {
addEventListener(cb) {
this.cb = cb;
},
click() {
this.cb();
},
};
example.addEventListener(person.greet);
example.click();
这时候调用 greet 中的指向就变成了 example:

这种方法也是很多时候 behind the scene 的实现,因此 this 的指向是相当不清晰的。
在一些情况下,这也就是 bind, call 和 apply 可以帮忙的地方。
bind
bind 会创建一个新的函数,同时 bind 会将 this 绑定成第一个传过去的参数。
遥想当初使用 React 的 class based component 时,在没有使用 arrow function 的时候,都是使用 bind 去执行的:
class ExplainBindingsComponent extends Component {
constructor() {
super();
this.onClickMe = this.onClickMe.bind(this);
}
onClickMe() {
console.log(this);
}
}
这就是为了将方法中的 this 绑定到当前类中,使其不随从就近原则。这里的第一个参数就是想要绑定的对象,在无所谓第绑定的对象时可以用 null,使用 this 指代的是当前对象,也可以明确的使用一个已经实例化的对象。
上面的几个案例使用 bind 绑定的改写方法为:
// 'use strict';
const person = {
name: 'John',
greet() {
console.log(this);
console.log('Hello ' + this.name);
},
};
const { greet } = person;
greet();
greet.bind(person)();
const user = {
name: 'user',
};
user.greet = person.greet.bind(person);
user.greet();
const example = {
addEventListener(cb) {
this.cb = cb;
},
click() {
this.cb();
},
};
example.addEventListener(person.greet.bind(person));
example.click();



这样都可以通过 bind 将 this 绑定到固定的对象上,使其不受就近原则的影响。另外,bind 也可以接受其他的参数,并将其传到调用的函数中,如:
const person = {
name: 'John',
greet(from) {
console.log(this);
console.log('Hello ' + this.name + ' from ' + from);
},
};
const { greet } = person;
// greet();
greet.bind(person, 'Sam')();
在一些情况下,使用 bind 也可以简化代码,如下面的代码可以实现一段基础的加减乘除的操作:
const math = {
accumulated: 0,
add(num) {
const oldNum = this.accumulated;
this.accumulated += num;
console.log(`${oldNum} + ${num} = ${this.accumulated}`);
},
substract(num) {
const oldNum = this.accumulated;
this.accumulated -= num;
console.log(`${oldNum} - ${num} = ${this.accumulated}`);
},
multiply(num) {
const oldNum = this.accumulated;
this.accumulated *= num;
console.log(`${oldNum} * ${num} = ${this.accumulated}`);
},
divide(num) {
const oldNum = this.accumulated;
this.accumulated /= num;
console.log(`${oldNum} / ${num} = ${this.accumulated}`);
},
};
math.add(10);
math.substract(5);
math.multiply(2);
math.divide(5);

这种情况下,可以使用 bind 去简化操作:
const math = {
accumulated: 0,
calculation(operation, num) {
let oldNum = this.accumulated;
if (operation === '+') {
this.accumulated += num;
} else if (operation === '-') {
this.accumulated -= num;
} else if (operation === '*') {
this.accumulated *= num;
} else if (operation === '/') {
this.accumulated /= num;
}
console.log(`${oldNum} ${operation} ${num} = ${this.accumulated}`);
},
};
const { calculation } = math;
calculation.bind(math, '+', 10)();
calculation.bind(math, '-', 5)();
calculation.bind(math, '*', 2)();
calculation.bind(math, '/', 5)();
最后的运行结果也是一致的:

不是说一定要用 bind 去执行,也可以用其他的方式使得代码更加清晰可读,并提高复用性
call & apply
call 和 apply 两个不会创建一个新的函数,但是它们的用法和 bind 相似,第一个参数都是 this 的指向,只不过 call 可以传递无限多的参数,而 apply 会接受一个 array like 的结构作为第二个参数:
call(thisArg, arg1, /* …, */ argN);
apply(thisArg, argsArray);
根据 mdn 所说,通常情况下,在不涉及到 constructed 的情况下,bind 和 call 是可以被视作效果相同的:
You can generally see
const boundFn = fn.bind(thisArg, arg1, arg2)as being equivalent toconst boundFn = (...restArgs) => fn.call(thisArg, arg1, arg2, ...restArgs)for the effect when it’s called (but not whenboundFnis constructed).
reference
- Function.prototype.bind()



















