JavaScript 中"对象即函数"设计模式

概念介绍

"对象即函数"设计模式是一种在 JavaScript 中实现的特殊模式,它允许一个对象实例同时具备函数调用和对象方法调用的能力。这种模式的核心在于通过 JavaScript 的原型链(prototype chain)和函数绑定(function binding)机制,使一个对象实例在被调用时能够执行特定的方法。

在 unified 框架中,这种设计模式主要体现在 Processor 类上,它既可以通过 new Processor() 创建实例,也可以通过 processor() 函数式调用来创建新的处理器实例。

javascript 复制代码
// 创建基础 unified 实例
export const unified = new Processor().freeze()

// 用户可以:
const processor = unified()  // 调用 copy 方法,返回新的 Processor
  .use(plugin)               // 使用链式方法
  .process(content)          // 处理内容

核心原理分析

Processor 类是 unified 框架的核心组件,负责管理文档处理流程的各个阶段。Processor 类继承自 CallableInstance:

javascript 复制代码
export const CallableInstance = function (property) {
  // 第一阶段:获取原型信息
  const self = this
  const constr = self.constructor  // 实例的构造函数
  const proto = constr.prototype   // 构造函数的原型对象
  const value = proto[property]    // 从原型中获取指定方法
  // 第二阶段:创建可调用函数
  const apply = function () {
    // 将 this 绑定到 apply 函数本身
    return value.apply(apply, arguments)
  }
  // 第三阶段:设置原型链
  Object.setPrototypeOf(apply, proto)

  return apply
}

而 Processor 类简化代码如下:

javascript 复制代码
export class Processor extends CallableInstance {
  constructor() {
    // 如果 Processor() 被调用(没有 new),则调用 copy 方法
    super('copy')
    // ... 其他初始化代码
  }

  copy() {
    // 创建新的处理器实例,复制所有配置
    const destination = new Processor()
    let index = -1
    while (++index < this.attachers.length) {
      const attacher = this.attachers[index]
      destination.use(...attacher)
    }
    destination.data(extend(true, {}, this.namespace))
    return destination
  }
}

Processor 实例化时,调用 CallableInstance,实现对象即函数调用的特性。当调用实例时,会触发 copy 方法执行,返回一个新的 Processor 实例。

应用场景分析

"对象即函数"模式在统一入口、可组合性和可复用性方面具有显著优势。

场景一:工厂函数 + 配置方法

链式配置后,通过一次函数调用触发常用动作。

javascript 复制代码
class DatabasePool extends CallableInstance {
  constructor() {
    super('copy') // pool() 将转发到 copy()
    this.config = { max: 5, timeout: 1000 }
  }
  setMaxConnections(n) { this.config.max = n; return this }
  copy() {
    const next = new DatabasePool();
    next.config = { ...this.config };
    return next
  }
}

const pool = new DatabasePool().setMaxConnections(10)
const isolated = pool() // 复制当前配置得到新实例

场景二:中间件系统

应用/路由对象既能装配中间件,又能作为 handler 被直接调用。

javascript 复制代码
class Router extends CallableInstance {
  constructor() {
    super('dispatch') // app(req, res) 将转发到 dispatch()
    this.stack = []
  }
  use(mw) { this.stack.push(mw); return this }
  dispatch(req, res) {
    let i = 0
    const next = () => { const mw = this.stack[i++]; if (mw) mw(req, res, next) }
    return next()
  }
}

const app = new Router().use(authMiddleware)
// 可直接作为 http.createServer(app) 的 handler

场景三:流处理管道

装配变换步骤,调用即运行整个管道。

javascript 复制代码
class DataPipeline extends CallableInstance {
  constructor() { super('run'); this.steps = [] }
  use(step) { this.steps.push(step); return this }
  run(input) {
    return this.steps.reduce((acc, fn) => fn(acc), input)
  }
}

const pipeline = new DataPipeline()
  .use(filterTransform)
  .use(mapTransform)

const result = pipeline(rawData) // 调用即执行

实现方式对比

传统函数扩展方式

javascript 复制代码
function createProcessor() {
  const processor = {
    use: function(plugin) {
      // 添加插件
      // 省略实现代码逻辑...
      return this
    },
    process: function(content) {
      // 处理内容
      // 省略代码实现逻辑...
      return result
    }
  }

  // 返回函数版本
  const callable = function() {
    return createProcessor()
  }

  // 复制方法
  Object.assign(callable, processor)

  return callable
}

缺点:

  • 每次创建都需要复制所有方法
  • 原型链断裂,失去继承优势
  • 内存使用效率低

Proxy 方式

javascript 复制代码
function createCallableProxy(instance, methodName) {
  return new Proxy(instance, {
    apply(target, thisArg, argumentsList) {
      return target[methodName].apply(target, argumentsList)
    }
  })
}

优缺点:

  • ✅ 实现简洁
  • ✅ 完全透明
  • ❌ Proxy 在某些环境下可能不被支持
  • ❌ 性能开销较大
  • ❌ 调试复杂度增加

CallableInstance 方式对比

实现方式 性能 兼容性 调试友好度 内存效率
传统函数扩展 优秀
Proxy 良好
CallableInstance 优秀 优秀

"对象即函数"不仅是一个设计模式,更是 JavaScript 灵活性思维的体现。 在合适的场景下应用这种模式,能为我们的代码带来更多的可能性和更好的用户体验。

相关推荐
AlienZHOU1 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
Captaincc4 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师6 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen6 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒7 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow7 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林8187 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang800787 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明7 小时前
第 6 章 组件进阶
前端·vue.js
江华森7 小时前
HTTP请求的完整过程详解:从DNS解析到TCP挥手的微秒级实战分析
前端