javascript

exercises

exercises.js
/**
 * 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.');
Exercises - JavaScript Tutorial | DeepML