Constructor interception
Basic Concepts of Constructor Interception in ECMAScript 6
ECMAScript 6 introduced the Proxy object, which can intercept and customize fundamental operations on objects. Constructor interception is an advanced application scenario of Proxy, allowing developers to intercept and modify class instantiation behavior. Through the construct
trap of Proxy, developers can intervene during the execution of the new
operator.
class TargetClass {
constructor(value) {
this.value = value;
}
}
const Handler = {
construct(target, args, newTarget) {
console.log(`Intercepted constructor call, arguments: ${args}`);
return new target(...args);
}
};
const ProxiedClass = new Proxy(TargetClass, Handler);
const instance = new ProxiedClass(42); // Output: "Intercepted constructor call, arguments: 42"
Detailed Parameters of the construct
Trap
The construct
trap receives three key parameters:
target
: The original constructor being proxiedargs
: An array of arguments passed to the constructornewTarget
: The constructor that was originally called (may differ in inheritance chains)
class Parent {
constructor(name) {
this.name = name;
}
}
class Child extends Parent {}
const Handler = {
construct(target, args, newTarget) {
console.log(target === Parent); // true
console.log(newTarget === Child); // true
return Reflect.construct(target, args, newTarget);
}
};
const ProxiedParent = new Proxy(Parent, Handler);
new Child('Alice', { proxy: ProxiedParent });
Implementing Singleton Pattern Interception
Constructor interception can be used to implement design patterns, such as enforcing a singleton:
class DatabaseConnection {
constructor(config) {
this.config = config;
}
}
let instance;
const SingletonHandler = {
construct(target, args) {
if (!instance) {
instance = Reflect.construct(target, args);
}
return instance;
}
};
const SingletonDB = new Proxy(DatabaseConnection, SingletonHandler);
const db1 = new SingletonDB({ host: 'localhost' });
const db2 = new SingletonDB({ host: '127.0.0.1' });
console.log(db1 === db2); // true, always returns the same instance
Parameter Validation and Transformation
Validate or transform parameters during construction:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const ValidationProxy = new Proxy(User, {
construct(target, args) {
const [name, age] = args;
if (typeof name !== 'string' || name.length < 2) {
throw new Error('Invalid username');
}
if (!Number.isInteger(age)) {
throw new Error('Age must be an integer');
}
const processedName = name.charAt(0).toUpperCase() + name.slice(1);
return Reflect.construct(target, [processedName, age]);
}
});
try {
const user = new ValidationProxy('alice', 25);
console.log(user.name); // "Alice"
} catch (e) {
console.error(e.message);
}
Dynamic Class Generation
Use constructor interception to generate classes dynamically:
function createTemporalClass(properties) {
return new Proxy(class {}, {
construct(target, args) {
const instance = {};
properties.forEach((prop, index) => {
instance[prop] = args[index];
});
return instance;
}
});
}
const TempClass = createTemporalClass(['id', 'title', 'timestamp']);
const tempObj = new TempClass(1, 'Temporary object', Date.now());
console.log(tempObj); // { id: 1, title: "Temporary object", timestamp: 1625097600000 }
Prototype Chain Control
Control prototype chain relationships through constructor interception:
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog {
bark() {
return 'Woof!';
}
}
const PrototypeProxy = new Proxy(Animal, {
construct(target, args) {
const instance = Reflect.construct(target, args);
Object.setPrototypeOf(instance, Dog.prototype);
return instance;
}
});
const hybrid = new PrototypeProxy('Buddy');
console.log(hybrid.name); // "Buddy"
console.log(hybrid.bark()); // "Woof!"
Asynchronous Initialization Interception
Implement an asynchronous constructor pattern:
class AsyncInit {
constructor(data) {
this.data = data;
this.initialized = false;
}
}
const AsyncProxy = new Proxy(AsyncInit, {
construct(target, args) {
const instance = Reflect.construct(target, args);
return new Promise(async (resolve) => {
await new Promise(r => setTimeout(r, 1000)); // Simulate async operation
instance.initialized = true;
resolve(instance);
});
}
});
(async () => {
const instance = await new AsyncProxy({ key: 'value' });
console.log(instance.initialized); // true
})();
Constructor Redirection
Redirect constructor calls to other constructors:
class Original {
constructor(x) {
this.x = x;
}
}
class Replacement {
constructor(x) {
this.value = x * 2;
}
}
const RedirectProxy = new Proxy(Original, {
construct(target, args) {
return Reflect.construct(Replacement, args);
}
});
const obj = new RedirectProxy(10);
console.log(obj instanceof Replacement); // true
console.log(obj.value); // 20
Metaprogramming Applications
Constructor interception can be used to implement various metaprogramming patterns:
class MetaExample {
constructor(value) {
this.value = value;
}
}
const loggingProxy = new Proxy(MetaExample, {
construct(target, args) {
console.log(`[${new Date().toISOString()}] Instantiating ${target.name}`);
const instance = Reflect.construct(target, args);
instance.creationTime = new Date();
return instance;
}
});
const metaObj = new loggingProxy(42);
// Output similar to: "[2023-01-01T12:00:00.000Z] Instantiating MetaExample"
console.log(metaObj.creationTime); // Creation time object
Performance Considerations and Limitations
While constructor interception is powerful, note the following:
- Proxy has higher performance overhead than direct object operations
- Certain low-level operations cannot be intercepted, such as
this
binding in strict mode - May affect code predictability
// Performance comparison test
class NormalClass {
constructor() {}
}
const ProxiedClass = new Proxy(NormalClass, {
construct(target, args) {
return Reflect.construct(target, args);
}
});
// Test normal construction
console.time('Normal construction');
for (let i = 0; i < 100000; i++) new NormalClass();
console.timeEnd('Normal construction');
// Test proxied construction
console.time('Proxied construction');
for (let i = 0; i < 100000; i++) new ProxiedClass();
console.timeEnd('Proxied construction');
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn