corehook-utility
Version:
This is a slicing programming tool class based on front-end objects or functions
236 lines (235 loc) • 8.52 kB
JavaScript
const GET_SUFFIX = '_get';
const SET_SUFFIX = '_set';
const DELETE_SUFFIX = '_delete';
const APPLY_SUFFIX = '_apply';
/**
* CoreHook is an advanced proxy utility for adding hooks before/after object operations and function calls
* Features: Intercept property access (get), property setting (set), property deletion (delete), and function invocation (apply)
*/
export class CoreHook {
/**
* Register hooks for property access (get) operations
* @param target Target object
* @param callbackList Array of callback functions
* @param before Whether to trigger before operation (default: true)
* @returns Proxy object
*/
static registerGetHook(target, callbackList, before = true) {
this.validateTarget(target, 'object');
this.validateCallbacks(callbackList);
const suffixName = `${GET_SUFFIX}${before}`;
this.saveHooks(target, callbackList, suffixName);
return this.createProxy(target, {
get(obj, prop) {
if (before) {
CoreHook.notifyHooks(target, suffixName, obj, prop);
}
const result = Reflect.get(obj, prop, obj);
if (!before) {
CoreHook.notifyHooks(target, suffixName, obj, prop);
}
return result;
}
});
}
/**
* Register hooks for property setting (set) operations
* @param target Target object
* @param callbackList Array of callback functions
* @param before Whether to trigger before operation (default: true)
* @returns Proxy object
*/
static registerSetHook(target, callbackList, before = true) {
this.validateTarget(target, 'object');
this.validateCallbacks(callbackList);
const suffixName = `${SET_SUFFIX}${before}`;
this.saveHooks(target, callbackList, suffixName);
return this.createProxy(target, {
set(obj, prop, value) {
if (before) {
CoreHook.notifyHooks(target, suffixName, obj, prop, value);
}
const success = Reflect.set(obj, prop, value, obj);
if (!before && success) {
CoreHook.notifyHooks(target, suffixName, obj, prop, value);
}
return success;
}
});
}
/**
* Register hooks for property deletion (delete) operations
* @param target Target object
* @param callbackList Array of callback functions
* @param before Whether to trigger before operation (default: true)
* @returns Proxy object
*/
static registerDeleteHook(target, callbackList, before = true) {
this.validateTarget(target, 'object');
this.validateCallbacks(callbackList);
const suffixName = `${DELETE_SUFFIX}${before}`;
this.saveHooks(target, callbackList, suffixName);
return this.createProxy(target, {
deleteProperty(obj, prop) {
if (before) {
CoreHook.notifyHooks(target, suffixName, obj, prop);
}
const success = Reflect.deleteProperty(obj, prop);
if (!before && success) {
CoreHook.notifyHooks(target, suffixName, obj, prop);
}
return success;
}
});
}
/**
* Register hooks for function invocation (apply) operations
* @param target Target function
* @param callbackList Array of callback functions
* @param before Whether to trigger before operation (default: true)
* @returns Proxy function
*/
static registerApplyHook(target, callbackList, before = true) {
this.validateTarget(target, 'function');
this.validateCallbacks(callbackList);
const suffixName = `${APPLY_SUFFIX}${before}`;
this.saveHooks(target, callbackList, suffixName);
return this.createProxy(target, {
apply(fn, thisArg, args) {
if (before) {
CoreHook.notifyHooks(target, suffixName, fn, thisArg, args);
}
const result = Reflect.apply(fn, thisArg, args);
if (!before) {
CoreHook.notifyHooks(target, suffixName, fn, thisArg, args);
}
return result;
}
});
}
/**
* Save hooks to storage
* @param target Target object or function
* @param callbacks Array of callback functions
* @param type Hook type identifier
*/
static saveHooks(target, callbacks, type) {
let typeMap = this.hooks.get(target);
if (!typeMap) {
typeMap = new Map();
this.hooks.set(target, typeMap);
}
if (!typeMap.has(type)) {
typeMap.set(type, []);
}
typeMap.get(type).push(...callbacks);
}
/**
* Trigger hook notifications
* @param target Target object or function
* @param type Hook type identifier
* @param args Arguments for callback functions
*/
static notifyHooks(target, type, ...args) {
try {
const typeMap = this.hooks.get(target);
if (!typeMap)
return;
const hooks = typeMap.get(type);
if (!hooks)
return;
for (const callback of hooks) {
try {
callback(...args);
}
catch (error) {
console.error(`Hook execution error: ${error}`);
}
}
}
catch (error) {
console.error(`Hook notification error: ${error}`);
}
}
/**
* Create proxy wrapper
* @param target Target object or function
* @param handler Proxy handler configuration
* @returns Proxy instance
*/
static createProxy(target, handler) {
// Use raw target if already a proxy
const rawTarget = this.getRawTarget(target);
// Create new proxy
const proxy = new Proxy(rawTarget, handler);
// Store proxy-to-target mapping
this.proxyToTarget.set(proxy, rawTarget);
return proxy;
}
/**
* Get original target (unwraps proxies)
* @param target Potential proxy object
* @returns Original target object
*/
static getRawTarget(target) {
let current = target;
while (this.proxyToTarget.has(current)) {
current = this.proxyToTarget.get(current);
}
return current;
}
/**
* Validate target type
* @param target Target to validate
* @param expectedType Expected type ('object' or 'function')
*/
static validateTarget(target, expectedType) {
if (!target) {
throw new TypeError('Target cannot be null or undefined');
}
if (expectedType === 'object' && (typeof target !== 'object' && typeof target !== 'function')) {
throw new TypeError('Target must be an object');
}
if (expectedType === 'function' && typeof target !== 'function') {
throw new TypeError('Target must be a function');
}
}
/**
* Validate callback list
* @param callbackList Callbacks to validate
*/
static validateCallbacks(callbackList) {
if (!Array.isArray(callbackList)) {
throw new TypeError('Callback list must be an array');
}
if (callbackList.length === 0) {
throw new TypeError('Callback list cannot be empty');
}
for (const cb of callbackList) {
if (typeof cb !== 'function') {
throw new TypeError('Callback list must contain only functions');
}
}
}
/**
* Clear all hooks for a target
* @param target Target object or function
*/
static clearTargetHooks(target) {
const rawTarget = this.getRawTarget(target);
this.hooks.delete(rawTarget);
}
/**
* Get all hooks for a target
* @param target Target object or function
* @returns Map of hook types and their callbacks
*/
static getTargetHooks(target) {
const rawTarget = this.getRawTarget(target);
return this.hooks.get(rawTarget);
}
}
// Use WeakMap to store hooks to prevent memory leaks
CoreHook.hooks = new WeakMap();
// Map proxies to their original targets
CoreHook.proxyToTarget = new WeakMap();