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 ageValidation 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 ageObservable 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 2Access 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: 5Extensibility 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.