作为前端初学者,手写 bind 和柯里化函数让我卡了很久,尤其是 this、instanceof、new 调用、参数收集这几个点。一步步拆解后终于理清了,整理成笔记,希望能帮到同样困惑的小伙伴。
一、手写柯里化(Currying)
1. 什么是柯里化?
柯里化是把一个多参数函数转换成一系列单参数函数的技术,比如 fn(1,2,3) 变成 fn(1)(2)(3)。它支持参数复用 、延迟执行 和函数组合。
2. 通用柯里化函数实现
2.1 我的代码
javascript
function currying(fn) {
// 形参数量
const len = fn.length;
return function collect(...args) {
// 如果已经收集够了,就执行原函数
if (args.length >= len) {
return fn.apply(this, args);
}
// 没收集够,继续返回一个新函数,继续收参数
return function (...newArgs) {
// 把之前的参数 + 新参数合并,递归调用 collect
return collect(...args, ...newArgs);
};
};
}
2.2 使用示例
javascript
function sum(a, b, c) {
return a + b + c;
}
const curriedSum = currying(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
console.log(curriedSum(1)(2, 3)); // 6
console.log(curriedSum(1, 2, 3)); // 6
const obj = {
x: 10,
add(a,b) {
return this.x + a + b
}
}
console.log(obj.add(1,2)) //13
const newCarry = carrying(obj.add)
console.log(newCarry.call(obj,1,2))//13
3. 柯里化应用场景
-
参数复用: 比如生成专门用于判断类型的函数
javascript//基础判断类型函数 function isType(type, value) { return Object.prototype.toString.call(value) === `[object ${type}]` } function currying(fn) { return function (type) { return function (value) { return fn(type, value) } } } const isString = currying(isType)('String') const isNumber = currying(isType)('Number') const isBoolean = currying(isType)('Boolean') console.log(isString('abc')) // true console.log(isString(123)) // false console.log(isNumber(123)) // true console.log(isBoolean(true)) // true -
延迟执行: 等所有参数准备完毕再执行,适合需要配置参数的场景。
javascriptfunction createRequest(baseURL, timeout, method) { console.log('发起请求:', { baseURL, timeout, method }) } const req = currying(createRequest) req('https://api.xxx.com') // 参数不够,继续收集 req(5000) // 不够 req('GET') // 够了,执行 -
函数组合: 配合 compose / pipe 实现数据流水线处理。
javascript//pipe const pipe = (...fns) => (val) => fns.reduce((data, fn) => fn(data), val) //compose const compose = (...fns) => (val) => fns.reduceRight((data, fn) => fn(data), val) // 柯里化工具 const curry = (fn) => function curried(...args) { if (args.length >= fn.length) return fn(...args) return (...nextArgs) => curried(...args, ...nextArgs) } // 准备一些处理函数 const add = curry((a, b) => a + b) const multiply = curry((a, b) => a * b) const subtract = curry((a, b) => a - b) const double = multiply(2) // 参数复用 const add10 = add(10) const sub5 = subtract(5) // 数据流程:先 +10 → 再 ×2 → 再 -5 const process = pipe( add10, double, sub5 ) console.log(process(20)) //55
二、手写 bind
bind 与 call / apply 的核心区别:
- call / apply 立即执行,bind 返回一个新函数。
- bind 支持预设参数(柯里化),还要处理 new 调用时忽略绑定 this 的特殊情况。
1. 手写 bind 逐行拆解
javascript
Function.prototype.myBind = function(context, ...bindArgs) {
const self = this; // 保存原函数
return function F(...args) {
if (this instanceof F) {
// new 调用:忽略绑定 this,返回原函数实例
return new self(...bindArgs, ...args);
}
// 普通调用:绑定 this 并执行
return self.apply(context, [...bindArgs, ...args]);
};
};
2. 疑惑与解答
2.1 const self = this 指向谁?
谁调用 myBind,this 就指向谁。比如 Student.myBind(obj),this 就是原函数 Student。保存是为了防止后续 this 丢失(因为返回的函数 F 里,this 会改变)。
2.2 if (this instanceof F) 在判断什么?
判断 this 是不是 F 的实例。
- **普通调用:**需要传入 context,通过 apply 把 this 绑定到该对象,返回的不是构造函数实例。
new调用: JS 自动创建新对象,this指向新实例,最终返回原构造函数的实例,bind绑定的this被忽略。
3. 调用示例验证
普通调用:
javascript
const obj = { name: 'Alice' };
function Person(age) {
this.age = age;
console.log(this.name); // undefined,因为 this 是 obj,obj 没有 name
}
const bound = Person.myBind(obj, 25);
bound(); // this 指向 obj,obj 上多了 age 属性
console.log(obj.age); // 25
new 调用:
javascript
function Person(name) {
this.name = name;
}
//new 调用
const BoundPerson = Person.bind({});
const p = new BoundPerson('666');
console.log(p instanceof Person); // true
三、总结
| 手写函数 | 核心要点 |
| 柯里化 | 利用 fn.length 获取形参个数,递归收集参数,达到阈值后执行原函数 |
| bind | 返回新函数,区分普通调用和 new 调用,new 时忽略绑定的 this,直接返回原函数实例 |
|---|