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 灵活性思维的体现。 在合适的场景下应用这种模式,能为我们的代码带来更多的可能性和更好的用户体验。

相关推荐
代码搬运媛2 小时前
Jest 测试框架详解与实现指南
前端
counterxing3 小时前
我把 Codex 里的 Skills 做成了一个 MCP,还支持分享
前端·agent·ai编程
wangqiaowq3 小时前
windows下nginx的安装
linux·服务器·前端
之歆4 小时前
DAY_12JavaScript DOM 完全指南(二):实战与性能篇
开发语言·前端·javascript·ecmascript
发现一只大呆瓜4 小时前
Vite凭什么这么快?3分钟带你彻底搞懂 Vite 热更新的幕后黑手
前端·面试·vite
Maimai108084 小时前
React如何用 @microsoft/fetch-event-source 落地 SSE:比原生 EventSource 更灵活的实时推送方案
前端·javascript·react.js·microsoft·前端框架·reactjs·webassembly
kyriewen6 小时前
产品经理把PRD写成“天书”,我用AI半小时重写了一遍,他当场愣住
前端·ai编程·cursor
humcomm6 小时前
元框架的工作原理详解
前端·前端框架
canonical_entropy6 小时前
Attractor Before Harness: AI 大规模开发的方法论
前端·aigc·ai编程
zhangxingchao7 小时前
多 Agent 架构到底怎么选?从 Claude Agent Teams、Cognition/Devin 到工程落地原则
前端·人工智能·后端