0
# Method Memoization
1
2
Specialized utilities for memoizing object methods with lazy property descriptors and proper `this` context handling. Ideal for prototype method optimization and instance-specific caching where methods need to be memoized on a per-instance basis.
3
4
## Capabilities
5
6
### Method Memoization with Property Descriptors
7
8
Create lazy property descriptors for memoizing object methods with proper `this` binding and instance-specific caching.
9
10
```javascript { .api }
11
/**
12
* Create memoized method property descriptors
13
* @param {Object} methods - Object mapping method names to descriptors
14
* @returns {Object} Lazy property descriptors for Object.defineProperties
15
*/
16
const memoizeMethods = require("memoizee/methods");
17
const descriptors = memoizeMethods(methodsObject);
18
```
19
20
**Usage Examples:**
21
22
```javascript
23
const memoizeMethods = require("memoizee/methods");
24
const d = require("d"); // Property descriptor helper
25
26
class DataProcessor {
27
constructor(data) {
28
this.data = data;
29
this.processed = false;
30
}
31
}
32
33
// Define memoized methods on prototype
34
Object.defineProperties(DataProcessor.prototype, memoizeMethods({
35
36
// Basic memoized method
37
calculateSum: d(function() {
38
console.log("Computing sum...");
39
return this.data.reduce((sum, val) => sum + val, 0);
40
}),
41
42
// Memoized method with options
43
expensiveTransform: d(function(transformType) {
44
console.log(`Performing ${transformType} transform...`);
45
return this.data.map(val => {
46
switch(transformType) {
47
case 'double': return val * 2;
48
case 'square': return val * val;
49
default: return val;
50
}
51
});
52
}, { maxAge: 60000 }), // Cache for 1 minute
53
54
// Async memoized method
55
fetchRelatedData: d(function(endpoint, callback) {
56
console.log(`Fetching from ${endpoint}...`);
57
setTimeout(() => {
58
callback(null, { endpoint, data: `data-for-${this.data[0]}` });
59
}, 100);
60
}, { async: true })
61
62
}));
63
64
// Usage
65
const processor1 = new DataProcessor([1, 2, 3, 4, 5]);
66
const processor2 = new DataProcessor([10, 20, 30]);
67
68
processor1.calculateSum(); // "Computing sum...", returns 15
69
processor1.calculateSum(); // Cache hit, returns 15 (no console output)
70
71
processor2.calculateSum(); // "Computing sum...", returns 60 (different instance)
72
processor2.calculateSum(); // Cache hit for processor2
73
74
// Each instance has its own cache
75
processor1.expensiveTransform('double'); // Computed for processor1
76
processor2.expensiveTransform('double'); // Computed for processor2 (different cache)
77
```
78
79
### Advanced Method Configuration
80
81
Configure memoized methods with complex options including custom normalizers and argument handling.
82
83
```javascript { .api }
84
/**
85
* Advanced method memoization options
86
*/
87
const descriptors = memoizeMethods({
88
methodName: d(function(...args) {
89
// Method implementation
90
}, {
91
// Standard memoization options
92
length: number,
93
primitive: boolean,
94
maxAge: number,
95
max: number,
96
async: boolean,
97
promise: boolean,
98
99
// Method-specific options
100
getNormalizer: function(length) {
101
// Return custom normalizer function
102
return function(args) {
103
return customKeyGeneration(args);
104
};
105
}
106
})
107
});
108
```
109
110
**Usage Examples:**
111
112
```javascript
113
const memoizeMethods = require("memoizee/methods");
114
const d = require("d");
115
116
class ApiClient {
117
constructor(baseUrl, apiKey) {
118
this.baseUrl = baseUrl;
119
this.apiKey = apiKey;
120
}
121
}
122
123
Object.defineProperties(ApiClient.prototype, memoizeMethods({
124
125
// Custom normalizer for complex object arguments
126
fetchUserData: d(function(userQuery) {
127
console.log("Fetching user data...");
128
return fetch(`${this.baseUrl}/users`, {
129
method: 'POST',
130
body: JSON.stringify(userQuery),
131
headers: { 'Authorization': `Bearer ${this.apiKey}` }
132
}).then(r => r.json());
133
}, {
134
promise: true,
135
maxAge: 300000, // 5 minute cache
136
getNormalizer: function(length) {
137
return function(args) {
138
// Normalize based on query content, not object identity
139
return JSON.stringify(args[0]);
140
};
141
}
142
}),
143
144
// Method with argument resolvers
145
processNumbers: d(function(num1, num2, operation) {
146
console.log(`Processing: ${num1} ${operation} ${num2}`);
147
switch(operation) {
148
case 'add': return num1 + num2;
149
case 'multiply': return num1 * num2;
150
default: return 0;
151
}
152
}, {
153
length: 3,
154
resolvers: [Number, Number, String], // Type coercion
155
maxAge: 30000
156
}),
157
158
// Async method with size limiting
159
heavyComputation: d(function(input, callback) {
160
console.log("Starting heavy computation...");
161
setTimeout(() => {
162
const result = input.map(x => x ** 2).reduce((a, b) => a + b, 0);
163
callback(null, result);
164
}, 1000);
165
}, {
166
async: true,
167
max: 50, // Limit cache size per instance
168
dispose: (result) => {
169
console.log("Disposing computation result:", result);
170
}
171
})
172
173
}));
174
175
// Usage
176
const client = new ApiClient("https://api.example.com", "key123");
177
178
client.fetchUserData({ name: "Alice", active: true });
179
client.fetchUserData({ active: true, name: "Alice" }); // Cache hit (normalized)
180
181
client.processNumbers("10", "5", "add"); // 15 (with type coercion)
182
client.processNumbers(10, 5, "add"); // Cache hit
183
```
184
185
### Instance-Specific Caching
186
187
Each object instance maintains its own separate cache for memoized methods.
188
189
```javascript { .api }
190
/**
191
* Instance isolation - each object gets its own method cache
192
* Methods are memoized per instance, not globally
193
*/
194
```
195
196
**Usage Examples:**
197
198
```javascript
199
const memoizeMethods = require("memoizee/methods");
200
const d = require("d");
201
202
class Calculator {
203
constructor(name) {
204
this.name = name;
205
this.calculations = 0;
206
}
207
}
208
209
Object.defineProperties(Calculator.prototype, memoizeMethods({
210
fibonacci: d(function(n) {
211
this.calculations++;
212
console.log(`${this.name}: Computing fib(${n})`);
213
if (n < 2) return n;
214
return this.fibonacci(n - 1) + this.fibonacci(n - 2);
215
})
216
}));
217
218
const calc1 = new Calculator("Calculator A");
219
const calc2 = new Calculator("Calculator B");
220
221
// Each instance has separate cache
222
calc1.fibonacci(10); // Computes and caches for calc1
223
calc2.fibonacci(10); // Computes and caches for calc2 (separate cache)
224
225
calc1.fibonacci(10); // Cache hit for calc1
226
calc2.fibonacci(10); // Cache hit for calc2
227
228
console.log(`${calc1.name} calculations: ${calc1.calculations}`);
229
console.log(`${calc2.name} calculations: ${calc2.calculations}`);
230
231
// Cache management per instance
232
calc1.fibonacci.clear(); // Clears only calc1's cache
233
calc2.fibonacci.delete(10); // Deletes only from calc2's cache
234
```
235
236
## Method Cache Management
237
238
### Per-Instance Cache Control
239
240
Access cache management methods on individual instances.
241
242
```javascript { .api }
243
/**
244
* Cache management methods available on each instance
245
*/
246
instance.methodName.delete(...args); // Delete specific cache entry
247
instance.methodName.clear(); // Clear all cached results for this instance
248
instance.methodName._get(...args); // Get cached value without execution
249
instance.methodName._has(...args); // Check if result is cached
250
```
251
252
**Usage Examples:**
253
254
```javascript
255
class DataAnalyzer {
256
constructor(dataset) {
257
this.dataset = dataset;
258
}
259
}
260
261
Object.defineProperties(DataAnalyzer.prototype, memoizeMethods({
262
analyzeData: d(function(analysisType) {
263
console.log(`Analyzing ${analysisType}...`);
264
return { type: analysisType, result: Math.random() };
265
}, { maxAge: 60000 })
266
}));
267
268
const analyzer = new DataAnalyzer([1, 2, 3]);
269
270
analyzer.analyzeData('mean'); // Computed
271
analyzer.analyzeData('median'); // Computed
272
273
// Check cache status
274
console.log(analyzer.analyzeData._has('mean')); // true
275
console.log(analyzer.analyzeData._has('mode')); // false
276
277
// Get cached value
278
const cachedMean = analyzer.analyzeData._get('mean');
279
console.log(cachedMean);
280
281
// Clear specific entry
282
analyzer.analyzeData.delete('mean');
283
284
// Clear all cached results for this instance
285
analyzer.analyzeData.clear();
286
```
287
288
### Reference Counting for Methods
289
290
Use reference counting with method memoization for sophisticated memory management.
291
292
```javascript { .api }
293
/**
294
* Reference counting methods for memoized methods
295
*/
296
const descriptors = memoizeMethods({
297
methodName: d(function(...args) {
298
// Method implementation
299
}, {
300
refCounter: true
301
})
302
});
303
304
// Additional methods available with refCounter
305
instance.methodName.deleteRef(...args); // Decrement reference
306
instance.methodName.getRefCount(...args); // Get reference count
307
```
308
309
**Usage Examples:**
310
311
```javascript
312
class ResourceManager {
313
constructor(id) {
314
this.id = id;
315
}
316
}
317
318
Object.defineProperties(ResourceManager.prototype, memoizeMethods({
319
createResource: d(function(resourceType) {
320
console.log(`Creating ${resourceType} resource...`);
321
return {
322
type: resourceType,
323
id: Math.random(),
324
cleanup: () => console.log(`Cleaning up ${resourceType}`)
325
};
326
}, {
327
refCounter: true,
328
dispose: (result) => {
329
if (result && result.cleanup) result.cleanup();
330
}
331
})
332
}));
333
334
const manager = new ResourceManager("mgr-1");
335
336
const resource1 = manager.createResource('database'); // refs: 1
337
const resource2 = manager.createResource('database'); // refs: 2 (cache hit)
338
339
console.log(manager.createResource.getRefCount('database')); // 2
340
341
manager.createResource.deleteRef('database'); // refs: 1
342
manager.createResource.deleteRef('database'); // refs: 0, cleanup called
343
```
344
345
## Advanced Method Patterns
346
347
### Lazy Initialization with Memoization
348
349
Combine lazy initialization with method memoization for optimal performance.
350
351
```javascript
352
class ExpensiveService {
353
constructor(config) {
354
this.config = config;
355
// Don't initialize expensive resources in constructor
356
}
357
}
358
359
Object.defineProperties(ExpensiveService.prototype, memoizeMethods({
360
361
// Lazy initialization - only run once per instance
362
initialize: d(function() {
363
console.log("Initializing expensive service...");
364
this.connection = createDatabaseConnection(this.config);
365
this.cache = new Map();
366
return this;
367
}),
368
369
// Use initialized resources
370
queryData: d(function(query) {
371
this.initialize(); // Ensure initialization (cached after first call)
372
console.log("Querying data...");
373
return this.connection.query(query);
374
}, {
375
async: true,
376
maxAge: 60000
377
})
378
379
}));
380
381
const service = new ExpensiveService({ host: 'localhost' });
382
// No expensive initialization yet
383
384
service.queryData('SELECT * FROM users'); // Initializes and queries
385
service.queryData('SELECT * FROM posts'); // Uses existing initialization
386
```
387
388
### Method Inheritance and Memoization
389
390
Handle method memoization in inheritance hierarchies.
391
392
```javascript
393
class BaseProcessor {
394
constructor(data) {
395
this.data = data;
396
}
397
}
398
399
// Base class memoized methods
400
Object.defineProperties(BaseProcessor.prototype, memoizeMethods({
401
baseProcess: d(function() {
402
console.log("Base processing...");
403
return this.data.length;
404
})
405
}));
406
407
class AdvancedProcessor extends BaseProcessor {
408
constructor(data, options) {
409
super(data);
410
this.options = options;
411
}
412
}
413
414
// Extended class additional memoized methods
415
Object.defineProperties(AdvancedProcessor.prototype, memoizeMethods({
416
advancedProcess: d(function(mode) {
417
console.log("Advanced processing...");
418
const baseResult = this.baseProcess(); // Uses memoized base method
419
return baseResult * (this.options.multiplier || 1);
420
}, { maxAge: 30000 })
421
}));
422
423
const advanced = new AdvancedProcessor([1, 2, 3], { multiplier: 2 });
424
advanced.baseProcess(); // Base method cached
425
advanced.advancedProcess('fast'); // Advanced method cached, reuses base result
426
```
427
428
## Performance Considerations
429
430
### Memory Usage Per Instance
431
432
Method memoization creates separate caches per instance:
433
434
```javascript
435
// Consider memory usage with many instances
436
const instances = [];
437
for (let i = 0; i < 1000; i++) {
438
const instance = new MyClass(data[i]);
439
instance.expensiveMethod(params); // Each instance gets its own cache
440
instances.push(instance);
441
}
442
443
// Use size limits for instances with large caches
444
Object.defineProperties(MyClass.prototype, memoizeMethods({
445
expensiveMethod: d(function(params) {
446
return heavyComputation(params);
447
}, {
448
max: 100, // Limit cache size per instance
449
maxAge: 300000 // Auto-expire entries
450
})
451
}));
452
```
453
454
### Choosing Method Memoization
455
456
Method memoization is ideal when:
457
458
- **Instance-specific results**: Results depend on instance state (`this` context)
459
- **Prototype optimization**: Want to add caching to existing class hierarchies
460
- **Per-instance cache management**: Need separate cache control per object
461
- **Object-oriented design**: Working with class-based architectures
462
463
Use regular memoization when:
464
465
- **Stateless functions**: Results don't depend on `this` context
466
- **Global caching**: Want shared cache across all calls
467
- **Functional programming**: Working with standalone functions