阿里云主机折上折
  • 微信号
Current Site:Index > Constructor interception

Constructor interception

Author:Chuan Chen 阅读数:39441人阅读 分类: JavaScript

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:

  1. target: The original constructor being proxied
  2. args: An array of arguments passed to the constructor
  3. newTarget: 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:

  1. Proxy has higher performance overhead than direct object operations
  2. Certain low-level operations cannot be intercepted, such as this binding in strict mode
  3. 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

上一篇:函数调用拦截

下一篇:撤销代理

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.