JavaScript 手写Function.prototype.bind() 底层逻辑以及解析
JavaScript 手写Function.prototype.bind() 底层逻辑以及解析
JavaScript Code:
Function.prototype.myCall = function(thisArg,...args) {
if(thisArg == null) {
thisArg = typeof window === 'undefined'? global : window;
}
const temp = Symbol('tempKey');
Object.defineProperty(thisArg,temp,{
value: this,
enumerable: false,
configurable: true,
})
const result = thisArg[temp](...args)
delete thisArg[temp];
return result;
}//手写的call方法
Function.prototype.myBind = function(thisArg,...args) {
if(thisArg == null) {
thisArg = typeof window === 'undefined'? global : window;
}
//返回一个绑定了this的新函数这里是箭头函数!应为this需要穿透出去拿到上一层作用域的this值也就是调用myBind的函数!
return (...parames)=>{
//这里的this指向的是调用myBind的函数
//这里需要用call来改变this指向然后我们这里手写一下call方法
return this.myCall(thisArg,...args,...parames)
}
}
//测试代码
const person = {
name: 'Alice',
}
function func(num1, num2, num3, num4) {
console.log(this)
console.log(num1, num2, num3, num4)
return num1 + num2 + num3 + num4;
}
const bindFunc = func.myBind(person, 10, 20)
const result = bindFunc(30, 40)
console.log(`返回值:${result}`)
1. 引言:理解原生Function.prototype.bind()
在解析手写实现前,需先明确原生bind()的核心功能——它是Function.prototype上的方法,用于创建一个新函数,该新函数的this被永久绑定到指定的thisArg,同时可预先传入部分参数(实现“柯里化”特性),后续调用新函数时传入的参数会与预传参数合并。
原生bind()的典型用法:
const obj = { name: "Bob" };
function sayHi(age) { console.log(`Hi ${this.name}, ${age}岁`); }
const boundSayHi = sayHi.bind(obj, 20); // 绑定this为obj,预传参数20
boundSayHi(); // 输出 "Hi Bob, 20岁"(this不变,参数生效)
手写myBind()的目标,就是模拟原生bind()的this绑定和参数柯里化核心逻辑,同时处理边界情况(如thisArg为null/undefined)。
2. 手写前置知识
实现myBind()需依赖以下JavaScript核心特性,需先明确其作用:
this绑定规则:函数的this指向由调用方式决定,需通过“间接调用”改变this(如obj.fn()中fn的this为obj)。- 剩余参数(…args):收集函数的不定长参数,转为数组,方便参数合并。
- Symbol:创建唯一的属性名,避免给
thisArg添加临时属性时覆盖其原有属性。 - Object.defineProperty:精细化配置对象属性(如
enumerable: false确保临时属性不被遍历,configurable: true确保后续可删除)。 - 箭头函数:无自身
this,继承外层作用域的this,用于固定myBind()返回函数的this指向。
3. 依赖实现:手写Function.prototype.myCall()
myBind()的核心逻辑依赖“改变函数this指向”,而call()是最直接的“改变this”的方法。因此需先实现myCall(),作为myBind()的底层依赖。
3.1 myCall()完整代码
Function.prototype.myCall = function(thisArg, ...args) {
// 1. 处理thisArg为null/undefined的情况
if (thisArg == null) {
thisArg = typeof window === 'undefined' ? global : window;
}
// 2. 创建唯一临时属性,避免覆盖thisArg原有属性
const temp = Symbol('tempKey');
// 3. 给thisArg添加临时属性,值为调用myCall()的函数(即原函数)
Object.defineProperty(thisArg, temp, {
value: this, // this指向调用myCall()的函数(如func.myCall()中this是func)
enumerable: false, // 临时属性不可枚举,避免遍历thisArg时暴露
configurable: true // 允许后续删除该属性
});
// 4. 间接调用原函数:thisArg[temp]() 使原函数的this指向thisArg,传入args参数
const result = thisArg[temp](...args);
// 5. 删除临时属性,避免污染thisArg
delete thisArg[temp];
// 6. 返回原函数的执行结果
return result;
};
3.2 myCall()逐行逻辑解析
| 代码片段 | 核心作用 | 细节说明 |
|---|---|---|
if (thisArg == null) { ... } |
处理边界:thisArg为null/undefined |
原生call()中,若thisArg为null/undefined,this会指向全局对象(浏览器端window,Node.js端global),此处模拟该行为。 |
const temp = Symbol('tempKey') |
避免属性名冲突 | Symbol生成的属性名是唯一的,不会覆盖thisArg上已有的属性(如thisArg.name不会被影响)。 |
Object.defineProperty(...) |
配置临时属性 | - value: this:将调用myCall()的函数(如func)挂载到thisArg的temp属性上;- enumerable: false:防止for...in或Object.keys()遍历到临时属性,符合原生call()“不污染thisArg”的特性;- configurable: true:确保后续能通过delete删除临时属性。 |
thisArg[temp](...args) |
改变this并执行函数 |
函数调用时,this指向调用者(即thisArg),同时传入args参数,实现“改变this+传参”的核心需求。 |
delete thisArg[temp] |
清理临时属性 | 执行完函数后删除临时属性,避免thisArg被永久污染。 |
return result |
传递返回值 | 原生call()会返回原函数的执行结果,此处保持一致。 |
4. 核心实现:手写Function.prototype.myBind()
myBind()的核心是返回一个绑定了this和预传参数的新函数,后续调用新函数时,会合并预传参数和新参数,最终通过myCall()执行原函数。
4.1 myBind()完整代码
Function.prototype.myBind = function(thisArg, ...args) {
// 1. 处理thisArg为null/undefined的情况(同myCall()逻辑)
if (thisArg == null) {
thisArg = typeof window === 'undefined' ? global : window;
}
// 2. 返回箭头函数:固定this指向,避免后续调用时this被改变
return (...parames) => {
// 3. 调用myCall():绑定this为thisArg,合并预传参数args和新参数parames
return this.myCall(thisArg, ...args, ...parames);
};
};
4.2 myBind()核心逻辑拆解
(1)边界处理:thisArg为null/undefined
与myCall()逻辑一致:若thisArg是null或undefined,则将this指向全局对象(window/global),模拟原生bind()的行为。
(2)返回箭头函数:固定this指向的关键
此处必须使用箭头函数,而非普通函数,原因是:
- 普通函数的
this由调用方式决定(如boundFunc.call(otherObj)会改变this); - 箭头函数无自身
this,其this继承自外层作用域(即myBind()执行时的this); myBind()执行时的this,正是调用myBind()的原函数(如func.myBind()中this是func)。
→ 最终效果:无论后续如何调用返回的新函数(如bindFunc()、bindFunc.call(otherObj)),其内部this始终指向原函数(func),确保myCall()能正确绑定thisArg。
(3)参数合并:实现柯里化特性
...args:收集myBind()调用时传入的预传参数(如func.myBind(person, 10, 20)中的10, 20);...parames:收集新函数调用时传入的后续参数(如bindFunc(30, 40)中的30, 40);...args, ...parames:通过“展开运算符”合并两组参数,最终传给原函数(如合并为[10, 20, 30, 40])。
→ 这就是bind()的“柯里化”特性:允许分多次传入参数,最终合并执行。
(4)调用myCall()并返回结果
通过this.myCall(...):
this指向原函数(func);- 绑定
this为thisArg(person); - 传入合并后的参数;
- 返回原函数的执行结果,确保新函数的返回值与原函数一致。
5. 测试代码执行流程解析
通过测试代码验证myBind()的正确性,需分步拆解执行过程:
5.1 测试代码
// 1. 定义被绑定的this对象
const person = { name: 'Alice' };
// 2. 定义原函数(需绑定this和参数)
function func(num1, num2, num3, num4) {
console.log(this); // 打印绑定后的this
console.log(num1, num2, num3, num4); // 打印合并后的参数
return num1 + num2 + num3 + num4; // 返回参数总和
}
// 3. 调用myBind():绑定this为person,预传参数10、20
const bindFunc = func.myBind(person, 10, 20);
// 4. 调用新函数:传入后续参数30、40,接收返回值
const result = bindFunc(30, 40);
// 5. 打印最终返回值
console.log(`返回值:${result}`);
5.2 分步执行流程
-
执行
func.myBind(person, 10, 20):myBind()的this指向func(原函数);thisArg为person(非null/undefined,无需指向全局);args收集预传参数[10, 20];- 返回一个箭头函数(即
bindFunc),该函数继承myBind()的this(func)。
-
执行
bindFunc(30, 40):- 箭头函数的
this继承自myBind(),即func; parames收集后续参数[30, 40];- 调用
this.myCall(...)→ 即func.myCall(person, 10, 20, 30, 40)。
- 箭头函数的
-
执行
func.myCall(...):thisArg为person,func的this指向person;- 传入参数
[10, 20, 30, 40],执行func; - 打印
this→{ name: 'Alice' }; - 打印参数 →
10 20 30 40; - 返回参数总和 →
10+20+30+40=100。
-
最终输出:
console.log(result)→ 打印返回值:100。
6. 核心关键点总结
手写myBind()的核心是解决“固定this”和“参数柯里化”两大问题,需牢记以下关键点:
thisArg边界处理:null/undefined时指向全局对象,符合原生行为;- 箭头函数固定this:利用箭头函数无自身
this的特性,确保返回函数的this不被后续调用改变; - Symbol避免属性冲突:
myCall()中用Symbol创建临时属性,防止污染thisArg; - 参数合并(柯里化):通过剩余参数和展开运算符,分多次传入的参数最终合并执行;
- 无副作用:
myCall()中删除临时属性,避免thisArg被永久修改。
7. 与原生bind()的对比及注意事项
手写myBind()实现了原生bind()的核心功能,但存在以下简化(原生bind()更复杂):
- 基本类型的装箱处理:原生
bind()若thisArg是基本类型(如123、"test"),会自动转为对应包装对象(new Number(123)、new String("test")),手写版本未处理; - 构造函数场景:原生
bind()返回的函数若作为构造函数(new boundFunc()),this会指向新创建的实例(而非thisArg),手写版本未处理; - 原型链继承:原生
bind()返回的函数会继承原函数的原型链,手写版本未处理。
→ 手写版本适用于理解bind()的核心逻辑,生产环境需使用原生bind()(兼容性和功能更完善)。
最终测试输出结果
执行测试代码后,控制台会依次打印:
{ name: 'Alice' } // func的this指向person
10 20 30 40 // 合并后的参数
返回值:100 // 参数总和
该结果符合预期,证明手写myCall()和myBind()的逻辑正确。
更多推荐



所有评论(0)