javascript
examples
examples.js⚡javascript
/**
* 13.6 Proxy and Reflect - Examples
*/
// ============================================
// BASIC PROXY
// ============================================
console.log('=== Basic Proxy ===');
const target = { name: 'John', age: 30 };
const handler = {
get(target, property, receiver) {
console.log(`Getting ${property}`);
return target[property];
},
set(target, property, value, receiver) {
console.log(`Setting ${property} to ${value}`);
target[property] = value;
return true;
},
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // Logs get, returns "John"
proxy.age = 31; // Logs set
// ============================================
// GET TRAP - DEFAULT VALUES
// ============================================
console.log('\n=== Get Trap - Default Values ===');
const withDefaults = new Proxy(
{},
{
get(target, property) {
return property in target ? target[property] : `Default for ${property}`;
},
}
);
withDefaults.name = 'John';
console.log(withDefaults.name); // "John"
console.log(withDefaults.missing); // "Default for missing"
// Dictionary with default 0
const counter = new Proxy(
{},
{
get(target, property) {
return target[property] ?? 0;
},
}
);
counter.clicks = counter.clicks + 1;
counter.clicks = counter.clicks + 1;
console.log(counter.clicks); // 2
// ============================================
// SET TRAP - VALIDATION
// ============================================
console.log('\n=== Set Trap - Validation ===');
const validated = new Proxy(
{},
{
set(target, property, value) {
if (property === 'age') {
if (typeof value !== 'number') {
throw new TypeError('Age must be a number');
}
if (value < 0 || value > 150) {
throw new RangeError('Age must be between 0 and 150');
}
}
target[property] = value;
return true;
},
}
);
validated.name = 'John'; // OK
validated.age = 30; // OK
console.log('Validated:', validated);
try {
validated.age = 'thirty';
} catch (e) {
console.log('Error:', e.message);
}
// ============================================
// HAS TRAP - HIDE PROPERTIES
// ============================================
console.log('\n=== Has Trap - Hide Properties ===');
const privateProps = new Proxy(
{ name: 'John', _secret: 'hidden' },
{
has(target, property) {
if (property.startsWith('_')) {
return false;
}
return property in target;
},
}
);
console.log('name' in privateProps); // true
console.log('_secret' in privateProps); // false (hidden)
// ============================================
// DELETE TRAP - PROTECT PROPERTIES
// ============================================
console.log('\n=== Delete Trap - Protect Properties ===');
const protectedObj = new Proxy(
{ id: 1, name: 'John' },
{
deleteProperty(target, property) {
if (property === 'id') {
console.log('Cannot delete id');
return false;
}
delete target[property];
return true;
},
}
);
delete protectedObj.name; // OK
console.log('After deleting name:', protectedObj);
delete protectedObj.id; // Blocked
console.log('After trying to delete id:', protectedObj);
// ============================================
// OWNKEYS TRAP - FILTER PROPERTIES
// ============================================
console.log('\n=== OwnKeys Trap - Filter Properties ===');
const filteredKeys = new Proxy(
{
name: 'John',
age: 30,
_password: 'secret',
_token: 'abc123',
},
{
ownKeys(target) {
return Reflect.ownKeys(target).filter((key) => !key.startsWith('_'));
},
getOwnPropertyDescriptor(target, property) {
if (property.startsWith('_')) {
return undefined;
}
return Object.getOwnPropertyDescriptor(target, property);
},
}
);
console.log('Keys:', Object.keys(filteredKeys)); // ['name', 'age']
// ============================================
// APPLY TRAP - FUNCTION PROXY
// ============================================
console.log('\n=== Apply Trap - Function Proxy ===');
function sum(a, b) {
return a + b;
}
const loggedSum = new Proxy(sum, {
apply(target, thisArg, args) {
console.log(`Called with args: ${args}`);
const start = performance.now();
const result = Reflect.apply(target, thisArg, args);
const end = performance.now();
console.log(`Result: ${result} (took ${(end - start).toFixed(4)}ms)`);
return result;
},
});
loggedSum(5, 3);
// ============================================
// CONSTRUCT TRAP - CLASS PROXY
// ============================================
console.log('\n=== Construct Trap - Class Proxy ===');
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const TrackedPerson = new Proxy(Person, {
construct(target, args) {
console.log(`Creating Person with: ${args}`);
return Reflect.construct(target, args);
},
});
const john = new TrackedPerson('John', 30);
console.log('Created:', john);
// ============================================
// REFLECT API
// ============================================
console.log('\n=== Reflect API ===');
const obj = { name: 'John', age: 30 };
// Reflect.get
console.log('Reflect.get:', Reflect.get(obj, 'name'));
// Reflect.set
Reflect.set(obj, 'city', 'NYC');
console.log('After Reflect.set:', obj);
// Reflect.has
console.log('Reflect.has:', Reflect.has(obj, 'name'));
// Reflect.deleteProperty
Reflect.deleteProperty(obj, 'city');
console.log('After Reflect.deleteProperty:', obj);
// Reflect.ownKeys
console.log('Reflect.ownKeys:', Reflect.ownKeys(obj));
// ============================================
// PATTERN: VALIDATION PROXY
// ============================================
console.log('\n=== Pattern: Validation Proxy ===');
function createValidated(schema) {
return new Proxy(
{},
{
set(target, property, value) {
const validator = schema[property];
if (validator && !validator(value)) {
throw new TypeError(`Invalid value for ${property}: ${value}`);
}
target[property] = value;
return true;
},
}
);
}
const user = createValidated({
name: (v) => typeof v === 'string' && v.length > 0,
age: (v) => typeof v === 'number' && v >= 0,
email: (v) => /^.+@.+\..+$/.test(v),
});
user.name = 'John';
user.age = 30;
user.email = 'john@example.com';
console.log('Valid user:', { ...user });
// ============================================
// PATTERN: OBSERVABLE
// ============================================
console.log('\n=== Pattern: Observable ===');
function createObservable(target, onChange) {
return new Proxy(target, {
set(target, property, value, receiver) {
const oldValue = target[property];
const result = Reflect.set(target, property, value, receiver);
if (result && oldValue !== value) {
onChange(property, oldValue, value);
}
return result;
},
});
}
const state = createObservable({ count: 0 }, (prop, oldVal, newVal) => {
console.log(`[State] ${prop}: ${oldVal} -> ${newVal}`);
});
state.count = 1;
state.count = 2;
state.count = 2; // No change, no log
state.message = 'Hello';
// ============================================
// PATTERN: NEGATIVE ARRAY INDICES
// ============================================
console.log('\n=== Pattern: Negative Array Indices ===');
function negativeArray(arr) {
return new Proxy(arr, {
get(target, property, receiver) {
const index = Number(property);
if (!isNaN(index) && index < 0) {
property = String(target.length + index);
}
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
const index = Number(property);
if (!isNaN(index) && index < 0) {
property = String(target.length + index);
}
return Reflect.set(target, property, value, receiver);
},
});
}
const arr = negativeArray([1, 2, 3, 4, 5]);
console.log('arr[-1]:', arr[-1]); // 5
console.log('arr[-2]:', arr[-2]); // 4
arr[-1] = 10;
console.log('After arr[-1] = 10:', [...arr]);
// ============================================
// PATTERN: AUTO-VIVIFICATION
// ============================================
console.log('\n=== Pattern: Auto-vivification ===');
function autoVivify() {
return new Proxy(
{},
{
get(target, property) {
if (!(property in target)) {
target[property] = autoVivify();
}
return target[property];
},
}
);
}
const data = autoVivify();
data.users.admin.permissions.read = true;
data.users.admin.permissions.write = true;
console.log('Deep structure:', JSON.stringify(data, null, 2));
// ============================================
// PATTERN: MEMOIZATION
// ============================================
console.log('\n=== Pattern: Memoization ===');
function memoize(fn) {
const cache = new Map();
return new Proxy(fn, {
apply(target, thisArg, args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('Cache hit for:', key);
return cache.get(key);
}
console.log('Computing for:', key);
const result = Reflect.apply(target, thisArg, args);
cache.set(key, result);
return result;
},
});
}
const fibonacci = memoize(function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
});
console.log('fib(10):', fibonacci(10));
console.log('fib(10) again:', fibonacci(10));
// ============================================
// PATTERN: LOGGING PROXY
// ============================================
console.log('\n=== Pattern: Logging Proxy ===');
function createLogger(target, name = 'Object') {
return new Proxy(target, {
get(target, property, receiver) {
console.log(`[${name}] GET ${String(property)}`);
const value = Reflect.get(target, property, receiver);
if (typeof value === 'object' && value !== null) {
return createLogger(value, `${name}.${String(property)}`);
}
return value;
},
set(target, property, value, receiver) {
console.log(
`[${name}] SET ${String(property)} = ${JSON.stringify(value)}`
);
return Reflect.set(target, property, value, receiver);
},
});
}
const logged = createLogger({ user: { settings: {} } });
logged.user.settings.theme = 'dark';
// ============================================
// REVOCABLE PROXY
// ============================================
console.log('\n=== Revocable Proxy ===');
const { proxy: revocableProxy, revoke } = Proxy.revocable(
{ name: 'John' },
{
get(target, property) {
return target[property];
},
}
);
console.log('Before revoke:', revocableProxy.name);
revoke();
try {
console.log(revocableProxy.name);
} catch (e) {
console.log('After revoke:', e.message);
}
// ============================================
// PRACTICAL: IMMUTABLE OBJECT
// ============================================
console.log('\n=== Practical: Immutable Object ===');
function createImmutable(obj) {
return new Proxy(obj, {
set() {
throw new Error('Cannot modify immutable object');
},
deleteProperty() {
throw new Error('Cannot delete from immutable object');
},
get(target, property, receiver) {
const value = Reflect.get(target, property, receiver);
if (typeof value === 'object' && value !== null) {
return createImmutable(value);
}
return value;
},
});
}
const immutable = createImmutable({ user: { name: 'John' } });
console.log('Can read:', immutable.user.name);
try {
immutable.user.name = 'Jane';
} catch (e) {
console.log('Cannot write:', e.message);
}
console.log('\n=== Examples Complete ===');