javascript
exercises
exercises.js⚡javascript
/**
* 13.6 Proxy and Reflect - Exercises
*/
/**
* Exercise 1: Default Values Proxy
* Create a proxy that returns default values for missing properties
*/
function withDefaults(target, defaults) {
// TODO: Return proxy that uses defaults for missing properties
// If property exists on target, return it
// Otherwise return from defaults object
}
// Test:
// const user = withDefaults({ name: 'John' }, { name: 'Guest', age: 0 });
// console.log(user.name); // "John"
// console.log(user.age); // 0
/**
* Exercise 2: Type Validator Proxy
* Create a proxy that validates property types
*/
function createTypedObject(schema) {
// TODO: Return proxy that validates types on set
// Schema format: { propertyName: 'type' }
// Types: 'string', 'number', 'boolean', 'object', 'array'
// Throw TypeError for invalid values
}
// Test:
// const person = createTypedObject({
// name: 'string',
// age: 'number',
// active: 'boolean'
// });
// person.name = 'John'; // OK
// person.age = 30; // OK
// person.age = 'thirty'; // TypeError
/**
* Exercise 3: Read-Only Proxy
* Create a proxy that makes an object completely read-only
*/
function createReadOnly(obj) {
// TODO: Return proxy that prevents all modifications
// Block: set, deleteProperty, defineProperty
// Make nested objects also read-only
}
// Test:
// const frozen = createReadOnly({ user: { name: 'John' } });
// frozen.user.name = 'Jane'; // Error or silently fail
// console.log(frozen.user.name); // "John"
/**
* Exercise 4: Private Properties Proxy
* Create a proxy that hides properties starting with _
*/
function hidePrivate(obj) {
// TODO: Return proxy that:
// - Returns undefined for _ properties on get
// - Throws on set to _ properties from outside
// - Hides _ properties from Object.keys, for...in, etc.
}
// Test:
// const obj = hidePrivate({ name: 'John', _password: 'secret' });
// console.log(obj.name); // "John"
// console.log(obj._password); // undefined
// console.log(Object.keys(obj)); // ['name']
/**
* Exercise 5: Array with Bounds Checking
* Create a proxy that throws on out-of-bounds access
*/
function boundedArray(arr) {
// TODO: Return proxy that:
// - Throws RangeError for out-of-bounds get/set
// - Supports negative indices (like Python)
// - Proxies length correctly
}
// Test:
// const arr = boundedArray([1, 2, 3]);
// console.log(arr[0]); // 1
// console.log(arr[-1]); // 3
// console.log(arr[10]); // RangeError
/**
* Exercise 6: Observable Object
* Create an observable that notifies on changes
*/
function createObservable(target) {
// TODO: Return object with:
// - proxy: the proxied object
// - subscribe(callback): add listener
// - unsubscribe(callback): remove listener
// Callbacks receive: { property, oldValue, newValue, type }
// type can be 'set' or 'delete'
}
// Test:
// const { proxy, subscribe } = createObservable({ count: 0 });
// subscribe(change => console.log('Changed:', change));
// proxy.count = 1; // Logs: Changed: { property: 'count', oldValue: 0, newValue: 1, type: 'set' }
/**
* Exercise 7: Method Chaining Proxy
* Create a proxy that enables method chaining for any object
*/
function chainable(obj) {
// TODO: Return proxy where any method returns the proxy
// allowing method chaining
}
// Test:
// const builder = chainable({
// value: '',
// add(str) { this.value += str; },
// space() { this.value += ' '; },
// getValue() { return this.value; }
// });
// const result = builder.add('Hello').space().add('World').getValue();
// console.log(result); // "Hello World"
/**
* Exercise 8: Memoized Function Proxy
* Create a proxy that memoizes function calls
*/
function memoize(fn) {
// TODO: Return proxy that caches results based on arguments
// Handle both primitive and object arguments
// Add cache.clear() method to clear cache
}
// Test:
// const expensive = memoize((n) => {
// console.log('Computing...');
// return n * 2;
// });
// expensive(5); // "Computing...", 10
// expensive(5); // 10 (cached)
// expensive.clear(); // Clear cache
/**
* Exercise 9: Revocable Access Token
* Create a system for managing temporary access to objects
*/
class AccessManager {
// TODO: Implement
// grant(obj, expiresIn): Return { proxy, token }
// The proxy works until revoked or expired
// revoke(token): Revoke access by token
// revokeAll(): Revoke all access
// isValid(token): Check if token is still valid
}
// Test:
// const manager = new AccessManager();
// const secret = { data: 'sensitive' };
// const { proxy, token } = manager.grant(secret, 5000);
// console.log(proxy.data); // "sensitive"
// manager.revoke(token);
// console.log(proxy.data); // Error
/**
* Exercise 10: Virtual Object
* Create a proxy that lazily loads properties on demand
*/
function createVirtualObject(loader) {
// TODO: Return proxy that:
// - Calls loader(property) when accessing unloaded properties
// - Caches loaded values
// - loader can return a Promise for async loading
// - Has .preload(...properties) method
}
// Test:
// const user = createVirtualObject(async (prop) => {
// console.log(`Loading ${prop}...`);
// return await fetchFromAPI(prop);
// });
// await user.name; // Loads name
// await user.name; // Uses cached value
/**
* BONUS CHALLENGES
*/
/**
* Bonus 1: Deep Reactive Object
* Create a deeply reactive object like Vue.js
*/
function reactive(target, effect) {
// TODO: Create proxy where any nested change triggers effect
// effect receives the path of changed property
}
/**
* Bonus 2: Schema Enforcing Proxy
* Create a proxy that enforces a complete schema
*/
function createSchemaProxy(schema) {
// TODO: Schema includes:
// - types for each property
// - required properties
// - custom validators
// - default values
// - nested schemas
}
/**
* Bonus 3: Debug Proxy
* Create a proxy for debugging object access patterns
*/
function createDebugProxy(target, name = 'obj') {
// TODO: Track and log all operations
// Provide .getStats() to see access patterns
// Track: get count, set count, property access frequency
}
// ============================================
// SOLUTION KEY (for reference)
// ============================================
/*
// Exercise 1 Solution:
function withDefaults(target, defaults) {
return new Proxy(target, {
get(target, property, receiver) {
if (property in target) {
return Reflect.get(target, property, receiver);
}
return defaults[property];
}
});
}
// Exercise 2 Solution:
function createTypedObject(schema) {
return new Proxy({}, {
set(target, property, value) {
const expectedType = schema[property];
if (expectedType) {
const actualType = Array.isArray(value) ? 'array' : typeof value;
if (actualType !== expectedType) {
throw new TypeError(
`Expected ${expectedType} for ${property}, got ${actualType}`
);
}
}
target[property] = value;
return true;
}
});
}
// Exercise 3 Solution:
function createReadOnly(obj) {
return new Proxy(obj, {
set() {
return false; // or throw new Error('Object is read-only');
},
deleteProperty() {
return false;
},
get(target, property, receiver) {
const value = Reflect.get(target, property, receiver);
if (typeof value === 'object' && value !== null) {
return createReadOnly(value);
}
return value;
}
});
}
// Exercise 4 Solution:
function hidePrivate(obj) {
return new Proxy(obj, {
get(target, property) {
if (typeof property === 'string' && property.startsWith('_')) {
return undefined;
}
return target[property];
},
set(target, property, value) {
if (typeof property === 'string' && property.startsWith('_')) {
throw new Error('Cannot set private property');
}
target[property] = value;
return true;
},
has(target, property) {
if (typeof property === 'string' && property.startsWith('_')) {
return false;
}
return property in target;
},
ownKeys(target) {
return Reflect.ownKeys(target).filter(
key => typeof key !== 'string' || !key.startsWith('_')
);
},
getOwnPropertyDescriptor(target, property) {
if (typeof property === 'string' && property.startsWith('_')) {
return undefined;
}
return Object.getOwnPropertyDescriptor(target, property);
}
});
}
// Exercise 5 Solution:
function boundedArray(arr) {
return new Proxy(arr, {
get(target, property, receiver) {
if (typeof property === 'string') {
const index = Number(property);
if (!isNaN(index)) {
const actualIndex = index < 0 ? target.length + index : index;
if (actualIndex < 0 || actualIndex >= target.length) {
throw new RangeError(`Index ${index} out of bounds`);
}
return target[actualIndex];
}
}
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
if (typeof property === 'string') {
const index = Number(property);
if (!isNaN(index)) {
const actualIndex = index < 0 ? target.length + index : index;
if (actualIndex < 0 || actualIndex >= target.length) {
throw new RangeError(`Index ${index} out of bounds`);
}
target[actualIndex] = value;
return true;
}
}
return Reflect.set(target, property, value, receiver);
}
});
}
// Exercise 6 Solution:
function createObservable(target) {
const listeners = new Set();
const proxy = new Proxy(target, {
set(target, property, value, receiver) {
const oldValue = target[property];
const result = Reflect.set(target, property, value, receiver);
if (result && oldValue !== value) {
for (const listener of listeners) {
listener({ property, oldValue, newValue: value, type: 'set' });
}
}
return result;
},
deleteProperty(target, property) {
const oldValue = target[property];
const result = Reflect.deleteProperty(target, property);
if (result) {
for (const listener of listeners) {
listener({ property, oldValue, newValue: undefined, type: 'delete' });
}
}
return result;
}
});
return {
proxy,
subscribe(callback) { listeners.add(callback); },
unsubscribe(callback) { listeners.delete(callback); }
};
}
*/
console.log('Complete the exercises above!');
console.log('Check the solution key at the bottom for guidance.');