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

相关推荐
xixichensh2 小时前
前端转做AI程序员第一步——开发一个AI流式对话Demo
前端
小羊没烦恼!2 小时前
系统内部模块(子系统)之间的耦合以及模块(子系统)划分
java·开发语言·前端·数据库·算法·c#
Leslie1652 小时前
聚合不只是 GROUP BY:HashAggregate 与 SortAggregate 的内存账本
前端
ITOM运维行者3 小时前
PHP性能监控怎么做?从响应时间到慢函数的6个关键指标
前端·javascript·后端
成旭先生3 小时前
AI 文本审核 API:一段中文文本判风险等级、命中标签与处置建议
java·前端·人工智能·api接口·内容风控·文本审核·ugc审核
liangshanbo12153 小时前
前端面试题:AI 对话中超长消息导致内存溢出,怎么解决?
java·开发语言·前端
静默回滚3 小时前
PDF能打开却不能复制,原来是权限密码
前端
律宏阔3 小时前
Dart FFI 内存管理:用 using + Arena 替代嵌套 try-finally
前端·flutter
guslegend3 小时前
脚手架入门:必要性、核心功能与执行原理
前端·架构·node.js·脚手架·前端工程化
律宏阔3 小时前
Flutter 调用 Go:从 c-shared + ffigen 到 @Native + Native Assets 踩坑记录
前端·flutter