第10天:手写 bind 与 柯里化 | 从疑惑到通透

作为前端初学者,手写 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
  • 延迟执行: 等所有参数准备完毕再执行,适合需要配置参数的场景。

    javascript 复制代码
    function 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,直接返回原函数实例
相关推荐
Amumu1213821 小时前
Js:正则表达式(一)
开发语言·javascript·正则表达式
无人机9011 天前
Delphi 网络编程实战:TIdTCPClient 与 TIdTCPServer 类深度解析
java·开发语言·前端
froginwe111 天前
CSS 图像拼合技术
开发语言
计算机安禾1 天前
【数据结构与算法】第22篇:线索二叉树(Threaded Binary Tree)
c语言·开发语言·数据结构·学习·算法·链表·visual studio code
a里啊里啊1 天前
测试开发面试题
开发语言·chrome·python·xpath
豆沙糕1 天前
Python异步编程从入门到实战:结合RAG流式回答全解析
开发语言·python·面试
信奥胡老师1 天前
P1255 数楼梯
开发语言·数据结构·c++·学习·算法
A.A呐1 天前
【C++第二十一章】set与map封装
开发语言·c++
扶苏-su1 天前
Java--获取 Class 类对象
java·开发语言
月光宝盒造梦师1 天前
Ant Design Ellipsis 中的判断逻辑 isEleEllipsis 方法非常消耗性能
javascript·react·优化