手撕 JS 核心:手动实现 bind 方法(从原理到完整代码)

在前端面试中,「手动实现 bind 方法」是高频考察点 —— 它不仅能验证你对 this 绑定、函数原型、构造函数等核心知识点的理解,还能体现代码的健壮性思维。本文会从 bind 的原生特性出发,拆解手写 bind 的核心逻辑,结合完整代码逐行解析,让你彻底搞懂背后的原理。

一、先搞懂:原生 bind 到底做了什么?

在手写之前,先明确原生 Function.prototype.bind 的核心特性,这是我们手写的「对标标准」:

  1. 返回新函数:bind 不会立即执行原函数,而是返回一个绑定了 this 的新函数;
  2. 绑定 this 指向:指定新函数执行时的 this 上下文,且绑定后无法被 call/apply 修改;
  3. 支持预置参数(柯里化):调用 bind 时可传入部分参数,新函数执行时可传入剩余参数;
  4. 兼容构造函数场景:如果绑定后的函数被当作构造函数(new 调用),this 会指向实例,而非 bind 传入的对象;
  5. 保留原型链:绑定后的新函数能继承原函数的原型属性。

举个原生 bind 的例子:

const person = { name: '张三' };
function sayHi(age, gender) {
  console.log(`我是${this.name},${age}岁,${gender}`);
}
// 绑定this+预置参数age=18
const bindSayHi = sayHi.bind(person, 18);
// 执行时传入剩余参数gender
bindSayHi('男'); // 输出:我是张三,18岁,男

二、手写 bind 完整代码(带详细注释)

基于原生特性,我们实现一个「功能对齐、鲁棒性强」的 myBind 方法,先看完整代码:

Function.prototype.myBind = function (obj, ...args) {
  // 1. 保存原函数(this 指向调用 myBind 的原函数,比如上面的 sayHi)
  const fn = this;

  // 2. 处理绑定对象:null/undefined 时绑定到 window,否则转为对象(兼容基本类型)
  obj = (obj !== null && obj !== undefined) ? Object(obj) : window;

  // 3. 定义返回的新函数 F,支持接收剩余参数
  const F = function (...rest) {
    // 4. 关键:区分构造函数场景(new 调用)和普通调用
    if (this instanceof F) {
      // new 调用时:this 指向 F 的实例,原函数作为构造函数执行
      return new fn(...args, ...rest);
    }
    // 普通调用时:用 call 绑定 this 到 obj,合并预置参数和新参数
    return fn.call(obj, ...args, ...rest);
  };

  // 5. 修复原型链:让新函数 F 继承原函数的原型
  F.prototype = Object.create(fn.prototype);
  // 修正 constructor 指向,避免原型链混乱
  F.prototype.constructor = F;

  // 6. 返回绑定后的新函数
  return F;
};

三、逐行拆解:核心知识点与实现逻辑

1. 保存原函数:const fn = this

  • 知识点:函数的 this 指向 —— 当调用 fn.myBind(obj) 时,this 指向调用 myBind 的原函数 fn(比如上面的 sayHi)。
  • 作用:后续执行新函数时,需要通过这个变量调用原函数,避免 this 丢失。

2. 处理绑定对象:obj = Object(obj) || window

  • 知识点this 的绑定规则 + 基本类型包装对象。
  • 细节
    • 如果传入的 objnull/undefined,按原生 bind 规则,this 绑定到全局对象(浏览器中是 window);
    • 如果传入的是基本类型(比如 123'abc'),用 Object() 转为包装对象(比如 Number {123}),避免 this 绑定到基本类型(基本类型无法作为 this 指向)。

3. 定义新函数 F:接收剩余参数

  • 知识点:剩余参数 ...rest + 函数柯里化。
  • 作用myBind 调用时传入的 ...args 是「预置参数」,新函数 F 执行时传入的 ...rest 是「剩余参数」,最终合并传给原函数(比如 fn(...args, ...rest)),实现柯里化。

4. 区分构造函数 / 普通调用

这是手写 bind 最核心的难点,也是面试高频考点:

  • 知识点new 操作符的原理 + instanceof 检测实例关系。
  • 逻辑解析
    • 当用 new 调用绑定后的函数(比如 new bindSayHi()),this 应该指向新创建的实例,而非 myBind 传入的 obj
    • this instanceof F:判断当前 this 是否是 F 的实例(即是否通过 new 调用);
    • 如果是构造函数场景:用 new fn(...args, ...rest) 执行原函数,此时原函数的 this 指向实例;
    • 如果是普通场景:用 fn.call(obj, ...args, ...rest) 执行原函数,强制绑定 thisobj

5. 修复原型链:避免断裂

  • 知识点:原型链继承 + Object.create 原理。
  • 问题:如果直接 F.prototype = fn.prototype,修改 F 的原型会污染原函数的原型;
  • 解决方案
    • F.prototype = Object.create(fn.prototype):创建一个以 fn.prototype 为原型的空对象,作为 F 的原型,实现「继承原型但不污染原原型」;
    • F.prototype.constructor = FObject.create 会导致 F.prototype 的 constructor 指向 fn,手动修正为 F,保证原型链的正确性。

四、测试验证:手写 myBind 是否符合预期

我们用原生 bind 的测试场景验证 myBind

const person = { name: '张三' };
function sayHi(age, gender) {
  console.log(`我是${this.name},${age}岁,${gender}`);
  // 构造函数场景:验证 this 指向实例
  this.age = age;
}
// 1. 普通调用场景
const myBindSayHi = sayHi.myBind(person, 18);
myBindSayHi('男'); // 输出:我是张三,18岁,男

// 2. 构造函数场景
const instance = new myBindSayHi('女');
console.log(instance.age); // 输出:18(this 指向实例,而非 person)
console.log(instance instanceof sayHi); // 输出:true(继承原函数原型)

五、核心知识点总结

手写 bind 的过程,本质是对以下 JS 核心知识点的综合运用:

  1. this 绑定规则:区分普通调用、构造函数调用的 this 指向;
  2. 函数的原型与原型链:通过 Object.create 实现原型继承,修正 constructor 指向;
  3. 剩余参数与柯里化:合并 bind 预置参数和新函数执行参数;
  4. new 操作符原理new 调用时 this 指向实例,且会继承原构造函数的原型;
  5. 基本类型包装对象:处理绑定对象为基本类型的边界场景。

六、面试加分点

在面试中手写 bind 时,除了写出核心逻辑,还可以补充这些「鲁棒性细节」,体现你的工程思维:

  1. 校验调用 myBind 的是否是函数:if (typeof this !== 'function') throw new TypeError('not a function')
  2. 兼容严格模式(严格模式下 null/undefined 不会转为 window);
  3. 解释 Object.create 替代方案(比如自定义空函数中转)。

最后

手动实现 bind 不是「背代码」,而是理解每一行背后的 JS 设计思想 —— 比如 this 的动态指向、原型链的继承逻辑、构造函数的特性。掌握这些底层逻辑,不仅能搞定面试,更能在日常开发中快速定位「this 丢失」「原型链混乱」等问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值