Back to JavaScript
2026-02-126 min read

Introspection Functions (JavaScript)

Learn Introspection Functions (JavaScript) step by step with clear examples and exercises.

Title: Introspection Functions (JavaScript) - A full guide to Understanding and Utilizing JavaScript's Reflect and Proxy Objects


Why This Matters

In JavaScript, introspection functions are a crucial tool that allows developers to inspect and manipulate objects at runtime. They are essential for understanding the inner workings of JavaScript, debugging complex applications, and even creating custom object behaviors. In this lesson, we will delve deep into two key introspection functions: Reflect and Proxy.


Prerequisites

Before diving into introspection functions, it's important to have a solid understanding of the following topics:

  1. JavaScript basics (variables, data types, operators, loops, functions)
  2. Object-oriented programming in JavaScript (properties, methods, constructors, prototypes)
  3. ES6 features (arrow functions, template literals, destructuring assignments, classes)
  4. Callbacks and Promises
  5. Understanding the event loop and asynchronous JavaScript
  6. Familiarity with advanced concepts like closures, hoisting, and scoping
  7. A strong foundation in object-oriented programming principles (inheritance, polymorphism, encapsulation)

Core Concept

Reflect Object

The Reflect object provides a reflection of built-in object methods that can be used to manipulate objects directly. It allows developers to access and modify the behavior of these methods without having to create custom wrappers or overriding built-in functions.

Here are some key methods in the Reflect object:

  1. Reflect.get(target, name[, receiver]) - Returns the value of a property on an object.
  2. Reflect.set(target, name, value[, receiver]) - Sets the value of a property on an object.
  3. Reflect.apply(constructor, thisArg, argumentsList) - Calls a constructor function with a specific this value and arguments list.
  4. Reflect.construct(constructor, argumentsList) - Creates a new instance of a constructor function with the provided arguments list.
  5. Reflect.defineProperty(target, name, attribute) - Defines or modifies a property on an object.
  6. Reflect.deleteProperty(target, name) - Deletes a property from an object.
  7. Reflect.has(target, name) - Checks if an object has a specific property.
  8. Reflect.ownKeys(target) - Returns the own and inherited property names of an object in an array.
  9. Reflect.isExtensible(target) - Checks if an object can be extended (i.e., new properties can be added).
  10. Reflect.preventExtensions(target) - Prevents further extension of an object.
  11. Reflect.getOwnPropertyDescriptor(target, name) - Returns the property descriptor for a specific property on an object.

Proxy Object

The Proxy object allows developers to define custom behavior for JavaScript objects by creating traps that intercept and handle certain actions on those objects. This enables the creation of advanced features like logging, data validation, and even custom object methods.

Here's how to create a basic Proxy:

const target = { hello: 'world' };
const handler = {
get(target, prop, receiver) {
console.log(`Getting ${prop}`);
return Reflect.get(...arguments);
},
set(target, prop, value, receiver) {
console.log(`Setting ${prop} to ${value}`);
return Reflect.set(...arguments);
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.hello); // Logs: Getting hello; Outputs: world

In this example, a Proxy is created for the target object with a custom get and set traps that log messages whenever properties are accessed or set on the proxy.


Worked Example

Let's create a simple class that uses both Reflect and Proxy to implement a custom logging behavior:

class LoggingClass {
constructor() {
this._value = null;
this._handler = {
get(target, prop, receiver) {
console.log(`Accessing ${prop}`);
return Reflect.get(...arguments);
},
set(target, prop, value, receiver) {
console.log(`Setting ${prop} to ${value}`);
return Reflect.set(...arguments);
}
};
}

getValue() {
return this._value;
}

setValue(value) {
this._value = value;
}

createProxy() {
const proxy = new Proxy(this, this._handler);
return proxy;
}
}

const loggingClassInstance = new LoggingClass();
const loggableProxy = loggingClassInstance.createProxy();

loggableProxy.setValue('Hello, World!');
console.log(loggableProxy.getValue()); // Logs: Accessing value; Outputs: Hello, World!

In this example, a LoggingClass is created that uses a Proxy with custom get and set traps to log messages whenever properties are accessed or set on an instance of the class. The createProxy() method returns a proxy that can be used for logging purposes.


Common Mistakes

  1. Forgetting to use Reflect methods when working with built-in object methods (e.g., using delete instead of Reflect.deleteProperty())
  2. Misunderstanding the difference between a target, receiver, and arguments in Proxy handlers
  3. Not properly defining all necessary traps in a Proxy handler (e.g., forgetting to implement has, ownKeys, or other traps)
  4. Using the wrong type of proxy for a specific use case (e.g., using a Proxy for simple data validation instead of creating a custom class)
  5. Overusing Proxy and Reflect in an attempt to overcomplicate solutions
  6. Neglecting to handle exceptions that may occur within Proxy handlers
  7. Failing to properly manage circular references when using Proxy

Practice Questions

  1. Write a Proxy that logs whenever a property is deleted from an object.
  2. Create a LoggingClass with a custom toString() method that logs messages when the class is converted to a string.
  3. Implement a Proxy that validates property values before they are set on an object, and throws an error if the value does not meet certain criteria (e.g., only allowing numbers between 1 and 10).
  4. Write a function that uses Reflect.apply() to call a constructor function with specific arguments and return the new instance.
  5. Create a Proxy that logs the time taken to execute a method on an object, using the Performance API.
  6. Implement a Proxy that allows for chaining of methods by returning the proxy itself in each method call.
  7. Write a Proxy that automatically deep-clones an object when it is set as a property value.
  8. Create a LoggingClass with a custom forEach() method that logs messages whenever the method is called.
  9. Implement a Proxy that caches the results of expensive computations to improve performance.
  10. Write a Proxy that prevents an object from being modified after a certain point in time, using the Date API.

FAQ

Q: Why use Reflect instead of directly accessing built-in object methods?

A: Using Reflect provides a consistent, cross-browser compatible way to interact with built-in object methods. It also allows developers to customize the behavior of these methods without having to create custom wrappers or overriding built-in functions.

Q: What are some common use cases for Proxy?

A: Some common use cases for Proxy include logging, data validation, creating custom object behaviors (e.g., implementing a read-only property), and performance optimization (e.g., caching results of expensive computations).

Q: Can I use Reflect and Proxy together in the same application?

A: Yes, it is possible to use both Reflect and Proxy in the same application for different purposes. For example, you might use Reflect to manipulate built-in object methods and create custom classes that use Proxy for advanced features like logging or data validation.

Q: Is it necessary to define all traps in a Proxy handler?

A: No, it is not necessary to define all traps in a Proxy handler. You can choose to implement only the specific traps that are relevant to your use case. However, be aware that omitting certain traps (e.g., has, ownKeys) may result in unexpected behavior.

Q: How do I handle exceptions within a Proxy handler?

A: You can throw an error from within a Proxy handler to signal that something went wrong. The error will be caught by the JavaScript runtime and handled appropriately (e.g., by propagating up the call stack or being logged in the console).

Q: How do I manage circular references when using Proxy?

A: To handle circular references, you can use a library like lodash or implement your own recursive function to traverse objects and avoid creating infinite loops. When creating a proxy for an object with potential circular references, make sure to pass the {depth: Infinity} option to the new Proxy() constructor to enable deep traversal.

Introspection Functions (JavaScript) | JavaScript | XQA Learn