In Jurassic Park, each enclosure has its own security system - fence voltage, alert level, incident counter. This data is private and inaccessible from outside, but the functions managing the enclosure have full access to it. This is exactly how closures work - they create a private environment for data, accessible only through specific functions.
A closure is a function that "remembers" variables from the scope in which it was created, even after that scope has ceased to exist.
1function createCounter() {
2 let count = 0; // Private variable - inaccessible from outside
3
4 return {
5 increment() { count++; return count; },
6 decrement() { count--; return count; },
7 getCount() { return count; },
8 };
9}
10
11const counter = createCounter();
12counter.increment(); // 1
13counter.increment(); // 2
14counter.getCount(); // 2
15// count is completely private - there is no direct access to itClosures allow you to create truly private variables in JavaScript - this is a key pattern in functional programming:
1function createSecuritySystem(zoneName) {
2 let fenceVoltage = 10000;
3 let alertLevel = 0;
4 let incidentLog = [];
5
6 // Private helper function
7 function logEvent(event) {
8 incidentLog.push({ event, time: Date.now(), zone: zoneName });
9 }
10
11 return {
12 raiseFence() {
13 fenceVoltage += 2000;
14 logEvent('Fence voltage raised to ' + fenceVoltage);
15 return fenceVoltage;
16 },
17 triggerAlert() {
18 alertLevel++;
19 logEvent('Alert level: ' + alertLevel);
20 return alertLevel;
21 },
22 getStatus() {
23 return {
24 zone: zoneName,
25 voltage: fenceVoltage,
26 alert: alertLevel,
27 incidents: incidentLog.length,
28 };
29 },
30 };
31}
32
33const zoneA = createSecuritySystem('Zone A');
34const zoneB = createSecuritySystem('Zone B');
35
36zoneA.raiseFence(); // 12000
37zoneA.triggerAlert(); // 1
38zoneB.getStatus(); // { zone: 'Zone B', voltage: 10000, alert: 0, incidents: 0 }
39// Each zone has its own isolated dataClosures are ideal for creating factories - functions that produce specialized functions:
1// Validator factory
2function createValidator(rules) {
3 return function validate(data) {
4 const errors = [];
5 for (const [field, rule] of Object.entries(rules)) {
6 if (rule.required && !data[field]) {
7 errors.push(field + ' is required');
8 }
9 if (rule.min && data[field] < rule.min) {
10 errors.push(field + ' must be >= ' + rule.min);
11 }
12 if (rule.max && data[field] > rule.max) {
13 errors.push(field + ' must be <= ' + rule.max);
14 }
15 }
16 return { valid: errors.length === 0, errors };
17 };
18}
19
20const validateDinosaur = createValidator({
21 name: { required: true },
22 weight: { required: true, min: 1, max: 100000 },
23 health: { required: true, min: 0, max: 100 },
24});
25
26validateDinosaur({ name: 'Rex', weight: 8000, health: 95 });
27// { valid: true, errors: [] }
28
29validateDinosaur({ name: '', weight: -5, health: 150 });
30// { valid: false, errors: ['name is required', 'weight must be >= 1', 'health must be <= 100'] }Memoization is a technique for caching results of expensive computations. Closures provide a private cache:
1function memoize(fn) {
2 const cache = new Map();
3
4 return function(...args) {
5 const key = JSON.stringify(args);
6 if (cache.has(key)) {
7 console.log('Cache hit for:', key);
8 return cache.get(key);
9 }
10 const result = fn(...args);
11 cache.set(key, result);
12 return result;
13 };
14}
15
16// Expensive DNA analysis
17function analyzeDNA(sampleId, depth) {
18 // Simulating a long computation
19 let result = 0;
20 for (let i = 0; i < depth * 1000; i++) {
21 result += Math.random();
22 }
23 return { sampleId, score: result / (depth * 1000) };
24}
25
26const memoizedAnalyze = memoize(analyzeDNA);
27memoizedAnalyze('TREX-001', 100); // computes
28memoizedAnalyze('TREX-001', 100); // from cache - instant!