返回

手写 call、apply、bind 浅析其本质与妙用

前端

this 上下文操控:callapplybind 的手把手进阶指南

在 JavaScript 的广阔世界中,this 上下文扮演着至关重要的角色,如果不小心处理,就会引发意想不到的运行时问题。为了应对这一挑战,callapplybind 这三位得力助手应运而生,它们能够娴熟地操控 this 上下文,助力我们编写更灵活、更健壮的代码。

手把手编写 callapplybind

掌握这三个函数的底层实现至关重要,它能让我们深入理解它们的机制并更有效地加以利用。

// 手写 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;
}

callapplybind 的妙用

了解了这三个函数的底层原理,我们再来看看它们在实际开发中的强大作用。

改变 this 指向

callapply 的一个主要用途是改变函数的 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. callapply 的区别是什么?

callapply 的主要区别在于传递参数的方式。call 接受参数列表,而 apply 接受一个参数数组。

2. 什么时候使用 bind

bind 通常用于柯里化函数,即创建新函数并预先绑定一些参数。

3. 如何改变函数的 this 指向?

可以使用 callapply 来改变函数的 this 指向。

4. callapplybind 是否支持箭头函数?

callapply 不支持箭头函数,因为箭头函数没有自己的 this 上下文。

5. callapplybind 的性能如何?

callapply 的性能略优于 bind,因为 bind 需要创建一个新的函数对象。

结语

callapplybind 是 JavaScript 中三个强大的函数,它们使我们能够灵活地操控 this 上下文,实现各种复杂的编程任务。通过掌握这些函数的用法,我们可以编写更优雅、更可维护的代码。