You will use JavaScript meta programming to inspect and control object behavior with Proxy, Reflect, Symbol, and new.target.
What it is
Meta programming is code that operates on code or object structure instead of only using normal values. In JavaScript, it lets you intercept property access, define hidden keys, and detect how a function is called. The main tools are Proxy for traps, Reflect for default behavior, Symbol for unique property keys, and new.target for constructor checks.
Why it matters
- Validate or log property reads and writes without changing every call site.
- Create private or metadata keys that do not collide with normal strings.
- Build frameworks, mocks, reactive state, and safe wrappers around objects.
- Preserve correct behavior by delegating to
Reflectafter custom logic.
Syntax or steps
- Create a target object.
- Create a handler with traps such as
get,set, orhas. - Call
Reflectinside the trap for normal behavior. - Wrap the target with
new Proxy(target, handler). - Use
Symbolfor special keys andnew.targetto require constructor use.
Example
const meta = Symbol.for('config.meta');
function Config(initial) {
if (!new.target) {
throw new TypeError('Config must be called with new');
}
const target = { ...initial };
const handler = {
get(obj, prop, receiver) {
if (prop === meta) {
return { created: new Date().toISOString() };
}
return Reflect.get(obj, prop, receiver);
},
set(obj, prop, value, receiver) {
if (prop === 'version' && typeof value !== 'number') {
throw new TypeError('version must be a number');
}
return Reflect.set(obj, prop, value, receiver);
},
has(obj, prop) {
return prop === meta || Reflect.has(obj, prop);
}
};
return new Proxy(target, handler);
}
const config = new Config({ name: 'app', version: 1 });
console.log(config.name);
console.log(config[meta]);
config.version = 2;
try {
config.version = 'bad';
} catch (error) {
console.log(error.message);
}
The Symbol.for key is unique but reusable by name. The constructor checks new.target so it cannot be called as a plain function. The get trap returns metadata for the symbol, then uses Reflect.get for normal properties. The set trap validates version, then uses Reflect.set. The has trap makes the symbol visible to in checks.
Common mistakes
- Forgetting to call
Reflectin a trap, which can break inheritance or setters. - Using a string key for metadata, which can collide with real properties.
- Returning a proxy from a constructor without understanding that the returned object replaces
this. - Calling a constructor without
newand not checkingnew.target.
When to use it
| Approach | Use when | Avoid when |
|---|---|---|
Proxy |
You need to intercept many operations on an object. | A simple getter or setter is enough. |
Object.defineProperty |
You need one property with custom access. | You need to intercept all properties or dynamic keys. |
Practice
- Guided: Add a
gettrap that logsReading namewhenpropisname, then returns the value withReflect.get. - Challenge: Add a
settrap that preventsnamefrom being changed after it is first set. Hint: store a flag on the target or use a closure variable.
Quick check
Question: Why use Reflect.get inside a Proxy get trap? Answer: It performs the normal property lookup while preserving the receiver and prototype chain.
Summary
JavaScript meta programming gives controlled access to object behavior. Use Proxy to intercept operations, Reflect to keep default semantics, Symbol for unique keys, and new.target for constructor safety.