第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,直接返回原函数实例
相关推荐
天若有情67315 小时前
反向封神!C++ 全局单例不避反用,实现无锁多线程函数独占访问
java·javascript·c++
凤凰院凶涛QAQ16 小时前
《C++转JAVA快速入手系列》:基本通用语法篇
java·开发语言·c++
zjun100116 小时前
QT:语言翻译
开发语言·qt
Shadow(⊙o⊙)16 小时前
C++常见错误解析2.0
开发语言·数据结构·c++·后端·学习·算法
谢谢 啊sir16 小时前
L2-057 姥姥改作业 - java
java·开发语言
l1t16 小时前
duckdb excel插件和rusty_sheet插件在python中的不同表现
开发语言·python·excel
人道领域16 小时前
【黑马点评日记】高并发秒杀:库存超卖与锁机制解析
java·开发语言·redis·spring·intellij-idea
lsx20240616 小时前
《jEasyUI 创建树形下拉框》
开发语言
minji...16 小时前
Linux 网络套接字编程(一)端口号port,socket套接字,socket,bind,socket 通用结构体
linux·运维·服务器·开发语言·网络
2301_8148098616 小时前
踩坑实战pywebview:用 Python + Web 技术打造轻量级桌面应用
开发语言·前端·python