Created
August 8, 2025 15:04
-
-
Save akingdom/e7c44549f1e39851bb2bc24d5015bf2f to your computer and use it in GitHub Desktop.
High-performance, hybrid property accessor for dot-delimited paths.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // pathGetter.js - MIT License (C) 2025 Andrew Kingdom | |
| /** | |
| * createPathGetter | |
| * ---------------- | |
| * Returns a high-performance, hybrid property accessor for dot-delimited paths. | |
| * | |
| * DESIGN: | |
| * - Cold paths (below `threshold` calls) use a manual string parse to avoid `.split()` allocations. | |
| * - Hot paths (≥ `threshold` calls) are promoted to precompiled accessors and cached for speed. | |
| * - Cache is capped (`maxCacheSize`) and evicts oldest hot paths (FIFO) to control memory growth. | |
| * | |
| * PERFORMANCE NOTES: | |
| * - Hot path avoids repeated path parsing entirely. | |
| * - Cold path uses a single-pass index scan instead of `.split()` for minimal GC churn. | |
| * - Uses a single Map for both hit counts and compiled functions to minimize lookups. | |
| * - FIFO eviction via array keeps cache ops simple and fast; replace with LRU if needed. | |
| * | |
| * PARAMETERS: | |
| * @param {number} threshold - Calls before a path is cached (default: 3) | |
| * @param {number} maxCacheSize - Max hot paths to cache (default: 200) | |
| * | |
| * RETURNS: | |
| * @returns {Function} get(obj, path) - Accesses nested property from `obj` via `path` | |
| * | |
| * USAGE: | |
| * const getPath = createPathGetter(); | |
| * const obj = { a: { b: 5 } }; | |
| * console.log(getPath(obj, 'a.b')); // → 5 | |
| */ | |
| function createPathGetter(threshold = 3, maxCacheSize = 200) { | |
| const cache = new Map(); // path -> { count, fn } | |
| const order = []; // FIFO order of hot paths | |
| return function get(obj, path) { | |
| let rec = cache.get(path); | |
| if (rec) { | |
| if (rec.fn) return rec.fn(obj); | |
| if (++rec.count === threshold) { | |
| // Promote to hot fn | |
| const keys = path.split('.'); // could inline manual parse here | |
| const fn = o => { | |
| for (let i = 0; i < keys.length; i++) o = o[keys[i]]; | |
| return o; | |
| }; | |
| rec.fn = fn; | |
| order.push(path); | |
| if (order.length > maxCacheSize) cache.delete(order.shift()); | |
| return fn(obj); | |
| } | |
| } else { | |
| cache.set(path, { count: 1, fn: null }); | |
| } | |
| // Cold path: manual parse to avoid split allocations | |
| let val = obj, start = 0, dot; | |
| while ((dot = path.indexOf('.', start)) !== -1) { | |
| val = val[path.slice(start, dot)]; | |
| start = dot + 1; | |
| } | |
| return val[path.slice(start)]; | |
| }; | |
| } | |
| // Example usage | |
| const getPath = createPathGetter(); | |
| const obj = { a: { b: 5 } }; | |
| console.log(getPath(obj, 'a.b')); // 5 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment