Back to Blog
JavaScriptReflectMeta-programmingProxy

JavaScript Reflect API Guide

Master the JavaScript Reflect API for meta-programming and proxy trap implementations.

B
Bootspring Team
Engineering
July 2, 2019
6 min read

The Reflect API provides methods for interceptable JavaScript operations, making it essential for Proxy implementations. Here's how to use it.

Basic Methods#

1const obj = { name: 'John', age: 30 }; 2 3// Get property 4console.log(Reflect.get(obj, 'name')); // 'John' 5 6// Set property 7Reflect.set(obj, 'age', 31); 8console.log(obj.age); // 31 9 10// Check property existence 11console.log(Reflect.has(obj, 'name')); // true 12 13// Delete property 14Reflect.deleteProperty(obj, 'age'); 15console.log(obj.age); // undefined 16 17// Get own property keys 18console.log(Reflect.ownKeys(obj)); // ['name']

Property Operations#

1const obj = {}; 2 3// Define property 4Reflect.defineProperty(obj, 'id', { 5 value: 1, 6 writable: false, 7 enumerable: true, 8 configurable: false, 9}); 10 11// Get property descriptor 12const desc = Reflect.getOwnPropertyDescriptor(obj, 'id'); 13console.log(desc); 14// { value: 1, writable: false, enumerable: true, configurable: false } 15 16// Check if property is defined 17console.log(Reflect.has(obj, 'id')); // true 18 19// Get all keys (including symbols) 20const sym = Symbol('key'); 21obj[sym] = 'symbol value'; 22console.log(Reflect.ownKeys(obj)); // ['id', Symbol(key)]

Prototype Operations#

1const animal = { type: 'animal' }; 2const dog = { breed: 'Labrador' }; 3 4// Set prototype 5Reflect.setPrototypeOf(dog, animal); 6 7// Get prototype 8console.log(Reflect.getPrototypeOf(dog)); // { type: 'animal' } 9 10// Check inheritance 11console.log(Reflect.has(dog, 'type')); // true (from prototype) 12console.log(Reflect.get(dog, 'type')); // 'animal'

Function Calls#

1function greet(greeting, punctuation) { 2 return `${greeting}, ${this.name}${punctuation}`; 3} 4 5const person = { name: 'Alice' }; 6 7// Apply function with this context 8const result = Reflect.apply(greet, person, ['Hello', '!']); 9console.log(result); // 'Hello, Alice!' 10 11// Compare to Function.prototype.apply 12const result2 = greet.apply(person, ['Hi', '?']); 13console.log(result2); // 'Hi, Alice?'

Constructor Calls#

1class User { 2 constructor(name, role) { 3 this.name = name; 4 this.role = role; 5 } 6} 7 8// Create instance with Reflect.construct 9const user = Reflect.construct(User, ['John', 'admin']); 10console.log(user); // User { name: 'John', role: 'admin' } 11 12// With different newTarget 13class Admin extends User {} 14 15const admin = Reflect.construct(User, ['Jane', 'superadmin'], Admin); 16console.log(admin instanceof Admin); // true 17console.log(admin); // Admin { name: 'Jane', role: 'superadmin' }

With Proxies#

1// Reflect methods are perfect for proxy traps 2const handler = { 3 get(target, prop, receiver) { 4 console.log(`Getting ${prop}`); 5 return Reflect.get(target, prop, receiver); 6 }, 7 8 set(target, prop, value, receiver) { 9 console.log(`Setting ${prop} to ${value}`); 10 return Reflect.set(target, prop, value, receiver); 11 }, 12 13 has(target, prop) { 14 console.log(`Checking ${prop}`); 15 return Reflect.has(target, prop); 16 }, 17 18 deleteProperty(target, prop) { 19 console.log(`Deleting ${prop}`); 20 return Reflect.deleteProperty(target, prop); 21 }, 22}; 23 24const obj = { name: 'Original' }; 25const proxy = new Proxy(obj, handler); 26 27proxy.name; // Getting name 28proxy.age = 25; // Setting age to 25 29'name' in proxy; // Checking name 30delete proxy.age; // Deleting age

Validation Proxy#

1const userSchema = { 2 name: (value) => typeof value === 'string' && value.length > 0, 3 age: (value) => typeof value === 'number' && value >= 0, 4 email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), 5}; 6 7const handler = { 8 set(target, prop, value, receiver) { 9 const validator = userSchema[prop]; 10 11 if (validator && !validator(value)) { 12 throw new Error(`Invalid value for ${prop}: ${value}`); 13 } 14 15 return Reflect.set(target, prop, value, receiver); 16 }, 17 18 defineProperty(target, prop, descriptor) { 19 const validator = userSchema[prop]; 20 21 if (validator && descriptor.value !== undefined) { 22 if (!validator(descriptor.value)) { 23 throw new Error(`Invalid value for ${prop}`); 24 } 25 } 26 27 return Reflect.defineProperty(target, prop, descriptor); 28 }, 29}; 30 31const user = new Proxy({}, handler); 32 33user.name = 'John'; // OK 34user.age = 30; // OK 35// user.age = -5; // Error: Invalid value for age

Observable Objects#

1function createObservable(target, onChange) { 2 return new Proxy(target, { 3 set(target, prop, value, receiver) { 4 const oldValue = Reflect.get(target, prop, receiver); 5 const result = Reflect.set(target, prop, value, receiver); 6 7 if (result && oldValue !== value) { 8 onChange(prop, value, oldValue); 9 } 10 11 return result; 12 }, 13 14 deleteProperty(target, prop) { 15 const hadProperty = Reflect.has(target, prop); 16 const oldValue = Reflect.get(target, prop); 17 const result = Reflect.deleteProperty(target, prop); 18 19 if (result && hadProperty) { 20 onChange(prop, undefined, oldValue); 21 } 22 23 return result; 24 }, 25 }); 26} 27 28const data = createObservable({ count: 0 }, (prop, newVal, oldVal) => { 29 console.log(`${prop} changed from ${oldVal} to ${newVal}`); 30}); 31 32data.count = 1; // count changed from 0 to 1 33data.count = 2; // count changed from 1 to 2

Access Control#

1function createPrivate(obj, privateProps = []) { 2 return new Proxy(obj, { 3 get(target, prop, receiver) { 4 if (privateProps.includes(prop)) { 5 throw new Error(`Cannot access private property: ${prop}`); 6 } 7 return Reflect.get(target, prop, receiver); 8 }, 9 10 set(target, prop, value, receiver) { 11 if (privateProps.includes(prop)) { 12 throw new Error(`Cannot modify private property: ${prop}`); 13 } 14 return Reflect.set(target, prop, value, receiver); 15 }, 16 17 has(target, prop) { 18 if (privateProps.includes(prop)) { 19 return false; 20 } 21 return Reflect.has(target, prop); 22 }, 23 24 ownKeys(target) { 25 return Reflect.ownKeys(target).filter( 26 (key) => !privateProps.includes(key) 27 ); 28 }, 29 30 getOwnPropertyDescriptor(target, prop) { 31 if (privateProps.includes(prop)) { 32 return undefined; 33 } 34 return Reflect.getOwnPropertyDescriptor(target, prop); 35 }, 36 }); 37} 38 39const user = createPrivate( 40 { name: 'John', password: 'secret', id: 1 }, 41 ['password'] 42); 43 44console.log(user.name); // 'John' 45// console.log(user.password); // Error 46console.log(Object.keys(user)); // ['name', 'id']

Lazy Initialization#

1function createLazy(initializer) { 2 let initialized = false; 3 let value; 4 5 return new Proxy({}, { 6 get(target, prop, receiver) { 7 if (!initialized) { 8 value = initializer(); 9 initialized = true; 10 } 11 return Reflect.get(value, prop); 12 }, 13 14 set(target, prop, newValue, receiver) { 15 if (!initialized) { 16 value = initializer(); 17 initialized = true; 18 } 19 return Reflect.set(value, prop, newValue); 20 }, 21 22 has(target, prop) { 23 if (!initialized) { 24 value = initializer(); 25 initialized = true; 26 } 27 return Reflect.has(value, prop); 28 }, 29 }); 30} 31 32const lazyConfig = createLazy(() => { 33 console.log('Loading config...'); 34 return { apiUrl: 'https://api.example.com', timeout: 5000 }; 35}); 36 37// Config not loaded yet 38console.log(lazyConfig.apiUrl); 39// Loading config... 40// 'https://api.example.com'

Method Interception#

1function createMethodLogger(obj) { 2 return new Proxy(obj, { 3 get(target, prop, receiver) { 4 const value = Reflect.get(target, prop, receiver); 5 6 if (typeof value === 'function') { 7 return function (...args) { 8 console.log(`Calling ${prop} with args:`, args); 9 const result = Reflect.apply(value, target, args); 10 console.log(`${prop} returned:`, result); 11 return result; 12 }; 13 } 14 15 return value; 16 }, 17 }); 18} 19 20const calculator = createMethodLogger({ 21 add: (a, b) => a + b, 22 multiply: (a, b) => a * b, 23}); 24 25calculator.add(2, 3); 26// Calling add with args: [2, 3] 27// add returned: 5

Extensibility Control#

1const obj = { name: 'John' }; 2 3// Check if extensible 4console.log(Reflect.isExtensible(obj)); // true 5 6// Prevent extensions 7Reflect.preventExtensions(obj); 8 9console.log(Reflect.isExtensible(obj)); // false 10 11// Can't add new properties 12const result = Reflect.set(obj, 'age', 30); 13console.log(result); // false in strict mode 14 15// Can still modify existing properties 16Reflect.set(obj, 'name', 'Jane'); 17console.log(obj.name); // 'Jane'

Best Practices#

Usage: ✓ Use with Proxy traps ✓ Maintain default behavior ✓ Handle return values ✓ Forward receiver properly Benefits: ✓ Consistent return values ✓ Matches Proxy trap signatures ✓ Better than Object methods ✓ Works with primitives Patterns: ✓ Validation proxies ✓ Observable objects ✓ Access control ✓ Method interception Avoid: ✗ Ignoring return values ✗ Forgetting receiver ✗ Using when unnecessary ✗ Breaking invariants

Conclusion#

The Reflect API provides methods that correspond to Proxy trap operations, making proxy implementations cleaner and more reliable. Use Reflect.get, Reflect.set, and other methods in proxy handlers to maintain default behavior while adding custom logic. The consistent return values and proper receiver handling make Reflect essential for meta-programming in JavaScript.

Share this article

Help spread the word about Bootspring