在 JS 中有三种方法可以改变 this 指向,分别为 call、apply 和 bind。这 3 种方法也是面试中经常要求会手写的面试题。
call
使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数
JavaScript Demo
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price);
this.category = 'food';
}
console.log(new Food('cheese', 5).name); // cheese
接下来我们就实现一个 call 方法
Function.prototype.myCall = function (context, args) {
// 不是函数不允许调用 call 方法
if (typeof this !== 'function') {
throw new Error('error');
}
// 如果传入的上下文是 null 或 undefined,则应该指向 window
context = context || window;
// 将要执行的函数挂到传入的上下文上,改变了 this
context.fn = this;
// 执行函数
let result = context.fn(args);
// 为了不给传入的上下文添加属性,执行完函数后需要删除
delete context.fn;
return result;
}
apply
apply 方法和 call 方法只有一个区别,就是它接受一个包含多个参数的数组
JavaScript Demo
const numbers = [5, 6, 2, 3, 7];
const max = Math.max.apply(null, numbers);
console.log(max); // 7
const min = Math.min.apply(null, numbers);
console.log(min); // 2
实现一个 apply 方法
Function.prototype.myApply = function (context, args) {
// 不是函数不允许调用 apply 方法
if (typeof this !== 'function') {
throw new Error('error');
}
// 如果传入的上下文是 null 或 undefined,则应该指向 window
context = context || window;
// 将要执行的函数挂到传入的上下文上,改变了 this
context.fn = this;
// 执行函数,这里使用了 ES6 的函数解构
let result = context.fn(...args);
// 为了不给传入的上下文添加属性,执行完函数后需要删除
delete context.fn;
return result;
}
bind
创建一个新的函数,在 bind 被调用时,这个新函数的 this 被指定为 bind 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
JavaScript Demo
const module = {
x: 42,
getX: function() {
return this.x;
}
}
const boundGetX = module.getX.bind(module);
console.log(boundGetX()); // 42
实现 bind
Function.prototype.myBind = function (context, ...args1) {
// 不是函数不允许调用 bind 方法
if (typeof this !== 'function') {
throw new Error('error');
}
let fn = this;
// 如果传入的上下文是 null 或 undefined,则应该指向 window
context = context || window;
let FToBind = function (...args2) {
// 合并 2 次传入的参数
let args = args1.concat(args2);
// 这里需要判断一下
// 应为有可能返回的函数被用作构造函数
// 当用作构造函数的时候就不能指向 context 了,需要指向 this
return fn.apply(this instanceof FToBind ? this : context, args);
}
// 维持原型链
if (this.prototype) {
FToBind.prototype = this.prototype;
}
return FToBind
}