UNPKG

@exadel/esl

Version:

Exadel Smart Library (ESL) is the lightweight custom elements library that provide a set of super-flexible components

86 lines (85 loc) 4.11 kB
import { defaultArgsHashFn, memoizeFn } from '../misc/memoize'; import { isPrototype, getPropertyDescriptor } from '../misc/object'; /** * `@memoize` decorator: caches method results or getter value. * - Supports prototype methods & getters and static methods & getters * - Prototype method: first call installs a per‑instance memoized function (independent caches per instance) * - Prototype getter: first access computes & stores value directly on the instance (value cache removable by deleting own prop) * - Static members: wrapped by shared `memoizeFn` (single cache per class member) * - Hash fn: string | null key; `undefined` means skip caching; default only supports 0-1 primitive arg * - Cache control: `memoize.clear(target, key)` removes cached value/function; `memoize.has(target, key, ...args)` checks presence * - Underlying memoized functions expose: `.cache: Map`, `.clear()`, `.has(...args)` * * @param hashFn - optional hash function; defaults to `defaultArgsHashFn` * @throws TypeError when applied to non-function / non-getter descriptor * @see memoizeFn low-level function memoizer */ export function memoize(hashFn = defaultArgsHashFn) { return function (target, prop, descriptor) { if (!descriptor || typeof (descriptor.value || descriptor.get) !== 'function') { throw new TypeError('Only get accessors or class methods can be decorated via @memoize'); } if (isPrototype(target)) { // Object members (typeof descriptor.get === 'function') && (descriptor.get = memoizeGetter(descriptor.get, prop)); (typeof descriptor.value === 'function') && (descriptor.value = memoizeMethod(descriptor.value, prop, hashFn)); } else { // Static members (typeof descriptor.get === 'function') && (descriptor.get = memoizeFn(descriptor.get)); (typeof descriptor.value === 'function') && (descriptor.value = memoizeFn(descriptor.value, hashFn)); } }; } // Lock storage to prevent cache logic for some key const locks = new WeakMap(); const defineOwnKeySafe = (obj, prop, value) => { locks.set(obj, prop); // IE try to get key with the prototype instance call, so we lock it Object.defineProperty(obj, prop, { value, writable: true, configurable: true }); locks.delete(obj); // Free property key }; /** Cache getter result as an object own property */ function memoizeGetter(originalMethod, prop) { return function () { if (locks.get(this) === prop) return originalMethod; const value = originalMethod.call(this); defineOwnKeySafe(this, prop, value); return value; }; } /** Cache method memo function in the current context on call */ function memoizeMethod(originalMethod, prop, hashFn) { return function (...args) { if (locks.get(this) === prop) return originalMethod; const memo = memoizeFn(originalMethod, hashFn); defineOwnKeySafe(this, prop, memo); return memo.apply(this, args); }; } function clearMemo(target, property) { if (Array.isArray(property)) return property.forEach((prop) => memoize.clear(target, prop)); const desc = getPropertyDescriptor(target, property); if (!desc) return; if (typeof desc.get === 'function' && typeof desc.get.clear === 'function') return desc.get.clear(); if (typeof desc.value === 'function' && typeof desc.value.clear === 'function') return desc.value.clear(); if (Object.hasOwnProperty.call(target, property)) delete target[property]; } memoize.clear = clearMemo; function hasMemo(target, property, ...params) { const desc = getPropertyDescriptor(target, property); if (!desc) return false; if (typeof desc.get === 'function' && typeof desc.get.has === 'function') return desc.get.has(...params); if (typeof desc.value === 'function' && typeof desc.value.has === 'function') return desc.value.has(...params); return Object.hasOwnProperty.call(target, property); } memoize.has = hasMemo;