object-hook
v0.1.0
Published
Path-based hooks for object methods in JavaScript and TypeScript.
Maintainers
Readme
object-hook
object-hook 是一个面向 JavaScript / TypeScript 的对象方法 Hook 工具库。
和常见的 hook / AOP 库相比,它最大的差异是:
- 它的核心能力是直接按对象路径 Hook 现有方法,并且可以批量处理多个方法
如果你的目标是增强一个已经存在的对象结构,比如 actions.save、lifecycles.ready、service.fetchUser 这类方法,object-hook 会比通用事件系统或重型 AOP 框架更直接。
它适合在不改业务调用方的前提下,为已有对象方法附加横切逻辑,比如:
- 日志和埋点
- 权限和前置校验
- 参数预处理
- 返回值转换
- 错误拦截和收尾逻辑
安装
npm install object-hook导入方式
推荐:
- ESM / TypeScript:
import {hook, createHook} from 'object-hook' - CommonJS:
const {hook, createHook} = require('object-hook')
推荐优先使用命名导出,这样在 ESM / TypeScript / CommonJS 场景下都更一致。
默认导出 hook 仍然可用:
import hook from 'object-hook';但作为公共文档和跨模块系统示例,优先使用命名导出更稳妥。
快速开始
import {hook} from 'object-hook';
const pageOptions = {
data: {
loaded: false
},
onLoad(query: Record<string, string>) {
console.log('page onLoad', query);
return query;
}
};
hook(pageOptions, 'onLoad', {
before(query) {
console.log('before onLoad', query);
},
afterReturn(result) {
return {
...result,
hooked: true
};
}
});
pageOptions.onLoad({from: 'share'});hook 做什么
hook() 是主入口。它会原地修改目标对象上的方法,并返回同一个对象。
你可以用它:
- Hook 单个方法
- Hook 多个路径,共用同一套 Hook 配置
- Hook 多个路径,并为每个路径指定不同配置
- 根据宿主对象动态生成 Hook 配置
支持的调用形式
Hook 单个方法
hook(service, 'fetchUser', {
before(id) {
console.log('before fetch', id);
}
});Hook 多个路径,共用同一套配置
hook(app, ['actions.save', 'actions.publish'], {
before() {
console.log('start action');
}
});Hook 多个路径,分别配置
hook(app, {
'actions.save': {
before() {
console.log('saving');
}
},
'lifecycles.ready': {
afterReturn(result) {
return result?.toUpperCase?.() ?? result;
}
}
});Hook 生命周期
单个方法的 Hook 配置支持四个回调:
before(...args)afterReturn(result, ...args)afterThrow(error, ...args)after(result, error, ...args)
调用时机
before
在原方法执行之前调用。
适合做:
- 参数检查
- 日志
- 计时开始
- 前置副作用
afterReturn
在原方法成功返回后调用。
适合做:
- 转换返回值
- 补充字段
- 记录成功日志
它的返回值会成为最终返回值。
afterThrow
在原方法抛错后调用。
适合做:
- 错误日志
- 监控上报
- 错误转换
如果 afterThrow 不重新抛出错误,则错误视为已被拦截,后续不会再抛出原错误。
after
在流程结束时调用。
适合做:
- 关闭计时
- 清理上下文
- 统一收尾
它不改变最终返回值。
调用顺序
成功路径:
before- 原方法
afterReturnafter- 返回最终结果
异常路径:
before- 原方法抛错
afterThrowafter- 如果
afterThrow没有重新抛错,则返回当前结果;否则继续抛错
顺序示例
hook(service, 'fetchUser', {
before(id) {
console.log('1.before', id);
},
afterReturn(result) {
console.log('3.afterReturn', result);
return {
...result,
loadedAt: Date.now()
};
},
afterThrow(error) {
console.log('3.afterThrow', error);
throw error;
},
after(result, error) {
console.log('4.after', {result, error});
}
});this 与 thisArg
默认情况下,原方法和 Hook 回调共享同一个 this。
const service = {
name: 'user-service',
fetchUser(id: string) {
return `${this.name}:${id}`;
}
};
hook(service, 'fetchUser', {
before(this: any, id) {
console.log(this.name, id); // user-service
}
});如果传入 thisArg,原方法和 Hook 回调都会在这个 thisArg 上执行。
const customThis = {name: 'custom-service'};
hook(service, 'fetchUser', {
before(this: any, id) {
console.log(this.name, id); // custom-service
}
}, customThis);严格模式
默认情况下,hook() 使用严格模式:
- 路径不存在会抛错
- 目标存在但不是函数会抛错
- 不会静默创建对象或空方法
hook(app, 'actions.missing', {
before() {}
});
// Error: Cannot hook "actions.missing": path does not existhook(app, 'config.data', {
before() {}
});
// Error: Cannot hook "config.data": target is not a function这种默认行为更适合业务代码,因为它能尽早暴露路径拼写错误和对象结构变更。
createHook(options)
createHook() 用来创建自定义 Hook 函数。
import {createHook} from 'object-hook';
const compatHook = createHook({
separator: '#',
allowMissing: true,
allowCreate: false
});支持这些选项:
separator: 路径分隔符,默认'.'allowMissing: 路径不存在时跳过,默认falseallowCreate: 路径不存在时创建缺失节点和目标方法,默认false
兼容模式
如果你需要兼容宽松行为,可以显式创建一个非严格版本:
const compatHook = createHook({
allowMissing: true,
allowCreate: true
});推荐做法:
- 新代码保持默认严格模式
- 只有在迁移旧代码或兼容动态对象结构时才开启
allowMissing/allowCreate
Builder 用法
当 hook() 的 decoration 是函数时,它收到的参数是宿主对象,不是被 Hook 的原方法。
这个函数不是 before 或 afterReturn 回调,而是一个 builder,用来生成真正的 Hook 配置。
它的返回值含义是:
- 返回
false:跳过当前路径,不做 Hook - 返回对象:这个对象会被当成真正的 Hook 配置
- 返回
undefined或null:等价于跳过当前路径,不做 Hook
这个 builder 只会在建立 Hook 时执行一次,不会在每次目标方法调用时重复执行。
hook(app, {
'actions.save'(host) {
return {
before() {
console.log(host);
}
};
}
});这适合按宿主对象状态动态决定是否启用 Hook,或者动态生成 Hook 配置。
hook(app, {
'actions.save'(host) {
return host.readonly
? false
: {
before() {
console.log('save allowed');
}
};
}
});如果 builder 返回 false、undefined 或 null,当前路径不会被 Hook。
其他 API
around(srcFunc, decoration, thisArg?)
为单个函数添加 before / afterReturn / afterThrow / after 生命周期 Hook。
import {around} from 'object-hook';
const wrapped = around(
() => {
throw new Error('Oops');
},
{
before: () => console.log('before'),
afterThrow: (error) => console.log('caught', error),
after: () => console.log('finally')
}
);decorate(srcFunc, decoration, thisArg?)
底层函数装饰器。你可以完全接管原函数调用过程。
wrap(srcFunc, decoration, thisArg?)
低级 API。根据 decoration 形态自动调用 decorate 或 around。
包消费说明
- ESM 环境优先走
exports.import,入口是dist/index.modern.mjs - CommonJS 环境走
exports.require,可直接通过命名导出拿到hook - 类型声明统一来自
dist/index.d.ts
推荐把命名导出当成跨模块系统最稳定的写法;默认导出仅作为兼容入口保留。
测试
npm test许可
MIT
