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 小时前
Vue 设计模式 实战指南
前端·javascript·vue.js·设计模式·ecmascript
芭拉拉小魔仙3 小时前
Vue项目中如何实现表格选中数据的 Excel 导出
前端·vue.js·excel
jump_jump3 小时前
妙用 localeCompare 获取汉字拼音首字母
前端·javascript·浏览器
U.2 SSD3 小时前
Echarts单轴坐标系散点图
前端·javascript·echarts
德育处主任Pro3 小时前
前端玩转大模型,DeepSeek-R1 蒸馏 Llama 模型的 Bedrock 部署
前端·llama
Jedi Hongbin4 小时前
Three.js NodeMaterial 节点材质系统文档
前端·javascript·three.js·nodematerial
前端小马4 小时前
前后端Long类型ID精度丢失问题
java·前端·javascript·后端
用户1456775610374 小时前
干净的图片批量处理,处理速度飞快
前端
用户1456775610374 小时前
亲测好用!简单实用的图片尺寸调整工具
前端
索西引擎4 小时前
npm、yarn、pnpm
前端·npm·node.js