当前位置: 首页 > news >正文

手写Function.prototype.bind:从原理到完整实现

在JavaScript中,Function.prototype.bind是处理this指向和参数预传的核心方法,它能创建一个绑定特定this的新函数,同时支持参数柯里化,还能兼容构造函数调用场景。很多开发者在使用bind时习以为常,但深入理解其底层逻辑并手动实现,不仅能应对面试中的高频考点,更能提升对this绑定和原型链的认知。本文将基于你提供的手写bind代码,从特性分析到实现拆解,全面讲解bind的手写思路与关键细节。

一、先明确:原生bind的核心特性

在手写前,必须先对齐原生bind的行为规范,确保实现的功能与原生一致。原生bind主要有3个核心特性:

  1. 绑定this:创建新函数时,将this固定为传入的第一个参数(thisArg),普通调用时不会被改变;
  2. 参数柯里化:支持预传部分参数(bind调用时传入的后续参数),新函数调用时可补充剩余参数,最终参数按顺序合并;
  3. 兼容构造函数:若新函数通过new作为构造函数调用,this会指向新创建的实例,而非bind时绑定的thisArg,且实例能继承原函数的原型方法。

举个原生bind的示例,直观感受其特性:

function Person(name, age) {this.name = name;this.age = age;
}
Person.prototype.sayHi = () => console.log(`Hi, ${this.name}`);// 1. 绑定this+预传参数
const obj = {};
const BoundPerson = Person.bind(obj, "Alice"); 
BoundPerson(20); // obj变为{name: "Alice", age: 20}// 2. 作为构造函数调用
const p = new BoundPerson(25); 
console.log(p); // Person {name: "Alice", age: 25}(this指向实例,非obj)
p.sayHi(); // Hi, Alice(继承原型方法)

二、手写bind的实现思路拆解

你提供的手写bind代码已经覆盖了核心逻辑,我们将其拆解为5个关键步骤,逐一解析每个环节的设计目的。

步骤1:校验调用者类型

bind是函数的原型方法,只能由函数调用,若调用者非函数(如数字、对象),需抛出TypeError,对齐原生行为:

Function.prototype.myBind = function (thisArg, ...args) {// 校验:若调用者不是函数,抛出错误if (typeof this !== "function") {throw new TypeError("not a function");}// 后续逻辑...
};

这里的this指向调用myBind的原函数(如上述示例中的Person),通过typeof this === "function"确保方法调用场景合法。

步骤2:保存原函数与预传参数

用变量self保存原函数(避免后续上下文变化导致this丢失),同时接收bind调用时传入的预传参数args(用于后续柯里化):

Function.prototype.myBind = function (thisArg, ...args) {if (typeof this !== "function") {throw new TypeError("not a function");}// 保存原函数(this指向原函数,如Person)const self = this;// 后续逻辑...
};

这一步是“闭包”的关键——selfargs会被后续返回的新函数引用,确保即使myBind执行完毕,这些变量仍能被访问。

步骤3:创建并返回绑定函数

核心逻辑集中在返回的bound函数中,它需要处理两种调用场景:普通调用和构造函数调用(new调用):

Function.prototype.myBind = function (thisArg, ...args) {if (typeof this !== "function") {throw new TypeError("not a function");}const self = this;// 创建绑定函数const bound = function (...newArgs) {// 1. 合并参数:预传参数(args)+ 新函数调用参数(newArgs)const allArgs = [...args, ...newArgs];// 2. 判断调用场景:是否通过new调用if (this instanceof bound) {// 场景1:作为构造函数调用(new BoundPerson())// 此时this指向新实例,需用new调用原函数,确保实例继承原函数属性return new self(...allArgs);} else {// 场景2:普通调用(BoundPerson(20))// 用apply绑定thisArg,传入合并后的参数return self.apply(thisArg, allArgs);}};// 后续逻辑...return bound;
};
关键细节解析:
  • 参数合并[...args, ...newArgs]实现柯里化,例如bind时传"Alice",新函数调用时传20,最终参数为["Alice", 20]
  • 调用场景判断this instanceof bound是核心——当用new调用bound时,this会指向新创建的实例,而实例的__proto__指向bound.prototype,因此this instanceof boundtrue
  • 普通调用处理:用self.apply(thisArg, allArgs)将原函数的this绑定为thisArg,同时传入合并后的参数;
  • 构造函数处理:用new self(...allArgs)调用原函数——此时原函数的this指向新实例,确保实例能继承原函数的属性(如nameage),而非绑定的thisArg

步骤4:原型链继承(确保构造函数实例的原型方法可用)

bound作为构造函数调用,实例需要继承原函数的原型方法(如Person.prototype.sayHi)。通过Object.create实现原型链连接,并修正constructor指向:

Function.prototype.myBind = function (thisArg, ...args) {if (typeof this !== "function") {throw new TypeError("not a function");}const self = this;const bound = function (...newArgs) {// 省略步骤3的逻辑...};// 1. 让bound的原型继承原函数的原型:确保实例能访问原函数原型方法bound.prototype = Object.create(self.prototype);// 2. 修正constructor指向:让实例的constructor指向bound(而非原函数)bound.prototype.constructor = bound;return bound;
};
为什么需要这一步?
  • 若不处理原型:new bound()创建的实例,其__proto__指向bound.prototype(默认是一个空对象),无法访问self.prototype上的方法(如sayHi);
  • Object.create(self.prototype):创建一个以self.prototype为原型的新对象,赋值给bound.prototype,此时实例的原型链为实例.__proto__ → bound.prototype → self.prototype,能正常访问原函数原型方法;
  • 修正constructorObject.create创建的新对象,其constructor会继承自self.prototype(即指向原函数self),手动将bound.prototype.constructor = bound,让实例的constructor逻辑更符合直觉(指向创建它的bound函数)。

步骤5:返回绑定函数

最终将bound函数返回,完成myBind的实现。此时bound就是一个绑定了this和预传参数的新函数,可支持普通调用和构造函数调用。

三、功能测试:验证手写bind的正确性

基于你提供的测试代码,我们验证手写myBind的功能是否与原生一致,覆盖“普通绑定”和“构造函数调用”两大场景。

测试1:普通绑定(绑定this+预传参数)

// 定义原函数
function Person(name, age) {this.name = name;this.age = age;
}
Person.prototype.sayHi = function () {console.log(`Hi, ${this.name}`);
};// 普通绑定:绑定this为obj,预传name为"Alice"
const obj = {};
const BoundPerson = Person.myBind(obj, "Alice");
// 调用BoundPerson,补充age为20
BoundPerson(20);// 验证结果:obj应包含name和age属性
console.log(obj); // { name: 'Alice', age: 20 } —— 符合预期

结论:普通调用时,this成功绑定为obj,预传参数"Alice"与新参数20合并,正确赋值给obj

测试2:作为构造函数调用(new调用)

// 用new调用BoundPerson,补充age为25
const p = new BoundPerson(25);// 验证1:实例p的属性
console.log(p); // Person { name: 'Alice', age: 25 } —— 符合预期(this指向实例,非obj)
// 验证2:实例p继承原型方法
p.sayHi(); // Hi, Alice —— 符合预期(能访问Person.prototype.sayHi)
// 验证3:constructor指向
console.log(p.constructor === BoundPerson); // true —— 符合预期(已修正constructor)

结论:构造函数调用时,this成功指向新实例p,实例能继承原函数的属性和原型方法,constructor指向正确。

四、手写bind的关键注意事项

在实现和使用手写bind时,有3个关键细节需要注意,避免出现功能偏差:

  1. thisArg的处理细节
    原生bind中,若thisArgnullundefined,在非严格模式下,this会指向全局对象(浏览器中为window,Node.js中为global);严格模式下仍为null/undefined。本文实现未额外处理这一场景,若需完全对齐原生,可在self.apply前添加逻辑:

    // 补充thisArg处理:null/undefined时,非严格模式指向全局对象
    const context = thisArg == null ? globalThis : Object(thisArg);
    return self.apply(context, allArgs);
    
  2. 原型链继承的必要性
    若省略“原型链继承”步骤(即不设置bound.prototype = Object.create(self.prototype)),实例将无法访问原函数的原型方法(如sayHi),这是新手最容易遗漏的点。

  3. constructor修正的可选性
    原生bind并未修正constructor指向(实例的constructor仍指向原函数),本文手动修正属于“优化项”,目的是让constructor逻辑更直观。若需完全模仿原生,可删除bound.prototype.constructor = bound这一行。

五、总结:手写bind的核心本质

手写bind的本质,是通过“闭包保存原函数与参数”“原型链继承确保实例方法可用”“区分调用场景处理this指向”三大核心逻辑,实现一个功能与原生对齐的绑定函数。

从代码结构来看,手写bind的关键在于:

  • 用闭包保留原函数和预传参数,确保后续调用时可访问;
  • this instanceof bound区分调用场景,灵活处理this指向(普通调用绑定thisArg,构造函数调用指向实例);
  • Object.create实现原型链继承,确保实例能访问原函数的原型方法。
http://www.dtcms.com/a/456863.html

相关文章:

  • 百度做网站的公司施工企业的施工现场消防安全责任人应是
  • 做电商网站报价网站开发工程师需要会写什么
  • (3)容器布局进阶:Spacer、Divider、Frame 与 Alignment
  • 墨西哥证券交易所(BMV)等多个交易所股票数据API对接文档
  • 【数据分析与可视化】2025年一季度金融业主要行业资产、负债、权益结构与增速对比
  • app网站建设阿里巴巴卓拙科技做网站吗
  • 乌苏市城乡建设局网站北京朝阳区邮政编码
  • 个人用云计算学习笔记 --18(NFS 服务器、iSCSI 服务器)
  • 智能制造——解读MES在各行业中的需求与解决方案【附全文阅读】
  • 老题新解|棋盘覆盖
  • 网站可不可以做自己的专利东莞沙田网站建设
  • Redis Hash 全解析:从入门到精通,解锁高性能对象存储的钥匙
  • 14.排序
  • Python自动化实战第一篇: 自动化备份100+台服务器Web 配置
  • 第五十二章 ESP32S3 UDP 实验
  • [鹤城杯 2021]Misc2
  • 山东省旅游网站建设网络设计是干什么的工作
  • 基于 ZYNQ ARM+FPGA+AI YOLOV4 的电网悬垂绝缘子缺陷检测系统的研究
  • 开源 C++ QT QML 开发(十二)通讯--TCP客户端
  • 【密码学实战】openHiTLS pkeyutl命令行:公钥实用工具(加解密、密钥交换)
  • 做标书有什么好的网站吗网站改版不收录
  • JDK17和JDK8的 G1
  • win10安装conda环境
  • TDengine 浮点数新编码 BSS 用户手册
  • mybatis call存储过程,out的参数怎么返回
  • 今日八股——JVM篇
  • 【论文阅读】REACT: SYNERGIZING REASONING AND ACTING IN LANGUAGE MODELS
  • 沈阳做网站比较好的公司做网站需要会的软件
  • ubuntu22.04安装gvm管理go
  • 基于单片机的智能点滴输液速度与液位控制系统设计