手写 bind 函数:12 分钟吃透(从差异到实现 + 面试考点)

25 阅读1分钟

上节课我们学了 callapply,它们的核心是「立即执行函数 + 改变 this 指向」;而 bind 不一样 —— 它的核心是「不立即执行,返回一个绑定了 this 指向的新函数」,还支持 “柯里化传参”(分多次传参)。

function sayHi(name, age) {
  console.log(`我是${this.name},你好${name},我${age}岁`);
  return `结果:${this.name}-${name}`;
}

const person = { name: "张三" };

// 1. bind 不立即执行,返回新函数
const boundSayHi = sayHi.bind(person, "小明"); // 先绑定 this+第一个参数
// 2. 后续调用新函数时,传入剩余参数
boundSayHi(20); // 输出:我是张三,你好小明,我20岁(this 已绑定 person)

// 柯里化传参:分两次传参,最终合并
boundSayHi(22); // 输出:我是张三,你好小明,我22岁

bind 最核心的特点是 “返回新函数”,而不是立即执行 —— 这也是手写 bindcall/apply 最大的区别!

Function.prototype.myBind = function (context) {
  // 步骤1:判断调用者是不是函数(和 call/apply 一致)
  if (typeof this !== 'function') {
    throw new TypeError('Error');
  }

  // 步骤2:保存原函数引用(this 就是原函数,比如 sayHi)和第一次传入的参数
  const fn = this; // 保存原函数,避免后续调用时 this 丢失
  const args = [...arguments].slice(1); // 截取 bind 调用时的参数(除了 context),比如 ["小明"]

  // 步骤3:返回新函数(延迟执行的核心,这是和 call/apply 最大的区别)
  return function Fn() {
    // 步骤4.1:合并两次传入的参数(bind 时的参数 + 新函数调用时的参数)
    // arguments 是新函数调用时的参数,比如 [20]
    const combinedArgs = args.concat(...arguments); // 合并为 ["小明", 20]

    // 步骤4.2:判断新函数是否被当作构造函数调用(用 new 实例化)
    // 关键:this instanceof Fn → 新函数被 new 时,this 是 Fn 的实例
    const bindContext = this instanceof Fn ? this : context;

    // 步骤4.3:用 apply 执行原函数(绑定 this+传合并后的参数)
    return fn.apply(bindContext, combinedArgs);
  };
};
function sayHi(name, age) {
  console.log(`我是${this.name},你好${name},我${age}岁`);
  return `结果:${this.name}-${name}`;
}

const person = { name: "张三" };
const boundSayHi = sayHi.myBind(person, "小明"); // 绑定 this+第一个参数

// 新函数调用,传入剩余参数
boundSayHi(20); // 输出:我是张三,你好小明,我20岁(this 正确,参数合并正确)
const res = boundSayHi(22);
console.log(res); // 结果:张三-小明(返回值正确)
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 用 bind 绑定 context(这里绑定 window,测试是否兼容 new)
const BoundPerson = Person.myBind(window, "李四");

// 用 new 实例化新函数
const p = new BoundPerson(25);

console.log(p.name); // 李四(this 指向实例,而非 window,兼容成功)
console.log(p.age); // 25(参数合并正确)
console.log(p instanceof Person); // true(实例属于原函数 Person,符合原生行为)