手写 call、apply、bind 浅析其本质与妙用
2023-10-09 03:49:08
this
上下文操控:call
、apply
、bind
的手把手进阶指南
在 JavaScript 的广阔世界中,this
上下文扮演着至关重要的角色,如果不小心处理,就会引发意想不到的运行时问题。为了应对这一挑战,call
、apply
和 bind
这三位得力助手应运而生,它们能够娴熟地操控 this
上下文,助力我们编写更灵活、更健壮的代码。
手把手编写 call
、apply
和 bind
掌握这三个函数的底层实现至关重要,它能让我们深入理解它们的机制并更有效地加以利用。
// 手写 call
function call(func, context, ...args) {
// 检查传参合法性
if (func === undefined || func === null) {
throw new TypeError("func is not a function");
}
// 确保 this 指向 context
const originalThis = this;
// 将 func 暂时挂载到 context 上,并调用 func
context[methodName] = func;
const result = context[methodName](...args);
// 恢复 this 指向
delete context[methodName];
return result;
}
// 手写 apply
function apply(func, context, args) {
// 检查传参合法性
if (func === undefined || func === null) {
throw new TypeError("func is not a function");
}
// 确保 this 指向 context
const originalThis = this;
// 将 func 暂时挂载到 context 上,并调用 func
context[methodName] = func;
const result = context[methodName](args);
// 恢复 this 指向
delete context[methodName];
return result;
}
// 手写 bind
function polyfillBind() {
function myBind(context, ...args) {
// 确保 this 指向 context
const that = this;
// 返回一个匿名闭包,这个闭包才是真正要被触发的
return function() {
return func.call(context, ...args, ...Array.prototype.slice.call(arguments));
}
}
return myBind;
}
call
、apply
和 bind
的妙用
了解了这三个函数的底层原理,我们再来看看它们在实际开发中的强大作用。
改变 this
指向
call
和 apply
的一个主要用途是改变函数的 this
指向。这在需要跨对象调用函数时非常有用。
function Person(name) {
this.name = name;
this.getName = function() {
console.log(this.name);
}
}
const person = new Person("alice");
const getName = person.getName;
// 输出 undefined,因为 getName 丢失了 this 指向
getName();
// 通过 call 改变 this 指向为 person
call(getName, person); // 输出 "alice"
实现继承
call
可以用来实现面向对象编程中的继承。子类可以调用父类的构造函数,从而继承父类的属性和方法。
function Parent(name) {
this.name = name;
}
Parent.prototype.getName = function() {
console.log(this.name);
}
function Child(name) {
// 调用父类的构造函数,实现继承
call(Parent, this, name);
}
const child = new Child("bob");
child.getName(); // 输出 "bob"
柯里化
bind
可以用于将一个多参函数柯里化为一个单参函数。柯里化可以简化函数调用,使代码更易于阅读和维护。
function sum(a, b) {
return a + b;
}
// 使用 bind 将 sum 方法柯里化为一个只接受一个实参的方法
const add10 = sum.bind(null, 10);
console.log(add10(5)); // 输出 15
** 实例演示**
为了进一步加深理解,我们再来看看一些实例演示。
改变 this
指向
改变 this
指向的能力非常强大,因为它允许我们在不同的上下文中使用同一个函数。
// 事件处理程序
const button = document.getElementById("my-button");
button.addEventListener("click", function() {
console.log(this); // 输出 <button id="my-button">...</button>
});
// 将事件处理程序的 this 指向改变为 window 对象
call(button.addEventListener, button, "click", function() {
console.log(this); // 输出 <window>
});
柯里化
柯里化可以极大地简化代码,使函数调用更具可读性和可维护性。
// 创建一个柯里化的函数,用于计算圆的面积
const calculateArea = radius => Math.PI * radius ** 2;
const areaOfCircle = calculateArea.bind(null, 5);
console.log(areaOfCircle()); // 输出 78.53981633974483
常见问题解答
1. call
和 apply
的区别是什么?
call
和 apply
的主要区别在于传递参数的方式。call
接受参数列表,而 apply
接受一个参数数组。
2. 什么时候使用 bind
?
bind
通常用于柯里化函数,即创建新函数并预先绑定一些参数。
3. 如何改变函数的 this
指向?
可以使用 call
和 apply
来改变函数的 this
指向。
4. call
、apply
和 bind
是否支持箭头函数?
call
和 apply
不支持箭头函数,因为箭头函数没有自己的 this
上下文。
5. call
、apply
和 bind
的性能如何?
call
和 apply
的性能略优于 bind
,因为 bind
需要创建一个新的函数对象。
结语
call
、apply
和 bind
是 JavaScript 中三个强大的函数,它们使我们能够灵活地操控 this
上下文,实现各种复杂的编程任务。通过掌握这些函数的用法,我们可以编写更优雅、更可维护的代码。