javascript

examples

examples.js
/**
 * 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 ===');
Examples - JavaScript Tutorial | DeepML