阿里云主机折上折
  • 微信号
Current Site:Index > The difference between the Constructor pattern and traditional classes

The difference between the Constructor pattern and traditional classes

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

The constructor pattern and traditional classes in JavaScript are both ways to create objects, but their implementation mechanisms and usage scenarios differ significantly. The constructor pattern relies on functions and prototype chains, while traditional classes are based on the ES6 class syntactic sugar. Although both ultimately achieve inheritance through prototypes, they differ in syntax, functionality extension, and detail handling.

Core Implementation of the Constructor Pattern

The constructor pattern simulates class behavior using ordinary functions. Instance properties are bound via this inside the function, while methods are attached to the function's prototype object. This pattern explicitly relies on the prototype chain and requires manual handling of prototype relationships:

function Animal(name) {
  this.name = name;
  this.speed = 0;
}

Animal.prototype.run = function(speed) {
  this.speed += speed;
  console.log(`${this.name} runs with speed ${this.speed}`);
};

const rabbit = new Animal('White Rabbit');
rabbit.run(5); // White Rabbit runs with speed 5

Key features include:

  1. Constructor names typically start with a capital letter.
  2. Instance properties are dynamically bound via this.
  3. Methods must be explicitly defined on prototype.
  4. Inheritance requires Parent.call(this) and manual setup of the child class prototype.

Syntax Structure of Traditional Classes

ES6's class provides syntax closer to traditional object-oriented languages but remains syntactic sugar over prototype inheritance:

class Animal {
  constructor(name) {
    this.name = name;
    this.speed = 0;
  }

  run(speed) {
    this.speed += speed;
    console.log(`${this.name} runs with speed ${this.speed}`);
  }
}

const rabbit = new Animal('Black Rabbit');
rabbit.run(10); // Black Rabbit runs with speed 10

Compared to the constructor pattern:

  1. Methods are defined directly within the class body.
  2. The constructor uses the fixed name constructor.
  3. Supports static methods.
  4. The property initializer proposal allows declaring properties directly at the class level.

Differences in Inheritance Implementation

Inheritance in the constructor pattern requires manually combining the prototype chain and constructor calls:

function Rabbit(name, earLength) {
  Animal.call(this, name);
  this.earLength = earLength;
}

Rabbit.prototype = Object.create(Animal.prototype);
Rabbit.prototype.constructor = Rabbit;

Rabbit.prototype.jump = function() {
  console.log(`${this.name} jumps!`);
};

Class inheritance simplifies the process with extends and super:

class Rabbit extends Animal {
  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  jump() {
    console.log(`${this.name} jumps!`);
  }
}

Key differences:

  • Class inheritance enforces super() before accessing this.
  • Class methods are non-enumerable by default.
  • The subclass's __proto__ points to the parent class (enabling static inheritance).

Internal Differences in Method Definitions

In the constructor pattern, methods added to the prototype are configurable and enumerable:

Object.getOwnPropertyDescriptor(Animal.prototype, 'run');
// {value: ƒ, writable: true, enumerable: true, configurable: true}

Class methods are non-enumerable by default:

class Animal {
  run() {}
}
Object.getOwnPropertyDescriptor(Animal.prototype, 'run');
// {value: ƒ, writable: true, enumerable: false, configurable: true}

Handling of Static Members

Static members in the constructor pattern are attached directly to the function object:

Animal.compare = function(a, b) {
  return a.speed - b.speed;
};

The class syntax provides the dedicated static keyword:

class Animal {
  static compare(a, b) {
    return a.speed - b.speed;
  }
}

Support for Private Fields

In modern JavaScript, classes support experimental private fields:

class Animal {
  #secret = 'hidden';
  getSecret() {
    return this.#secret;
  }
}

The constructor pattern requires closures or WeakMap to simulate privacy:

const Animal = (function() {
  const secrets = new WeakMap();
  
  function Animal(name) {
    secrets.set(this, { secret: 'hidden' });
    this.name = name;
  }

  Animal.prototype.getSecret = function() {
    return secrets.get(this).secret;
  };

  return Animal;
})();

Differences in Type Checking

Constructor pattern instances are checked via instanceof against the prototype chain:

rabbit instanceof Animal; // true

Class instances expose an internal [[IsClassConstructor]] marker in addition to instanceof:

class Animal {}
typeof Animal; // "function"
Animal.toString(); // "class Animal {...}"

Differences in Hoisting Behavior

Function declarations are hoisted, allowing the constructor pattern to be used before definition:

new Animal('Early Bird'); // Works
function Animal() {}

Class declarations have a temporal dead zone:

new Animal('Early Bird'); // ReferenceError
class Animal {}

Application Scenarios in Design Patterns

The constructor pattern is more flexible and suitable for scenarios requiring dynamic prototype modification:

function DynamicClass() {}
// Dynamically add methods at runtime
if (condition) {
  DynamicClass.prototype.method = function() {};
}

Classes are better suited for fixed structures, especially when clear inheritance is needed:

class Component {
  // Clear interface definition
}
class Button extends Component {
  // Explicit inheritance hierarchy
}

Performance and Memory Considerations

Modern JavaScript engines optimize both forms similarly. However, the constructor pattern may:

  1. Trigger hidden class changes when modifying prototypes.
  2. Affect inline caching when dynamically adding methods.
  3. Be harder to statically analyze in large projects.

The class syntax, due to its fixed structure, is easier for engines to optimize:

// Engines can pre-determine the class structure
class FixedStructure {
  method1() {}
  method2() {}
}

Differences in Metaprogramming Capabilities

Constructor functions, as ordinary functions, can be directly used with apply/call:

function Person(name) {
  this.name = name;
}
const obj = {};
Person.call(obj, 'Alice');

Class constructors check new.target and throw errors if called directly:

class Person {
  constructor(name) {
    if (!new.target) throw new Error();
    this.name = name;
  }
}
Person.call(obj, 'Bob'); // Error

Compatibility with the Decorator Proposal

The current decorator proposal only supports class syntax:

@decorator
class Animal {
  @methodDecorator
  run() {}
}

The constructor pattern requires higher-order functions to simulate decoration:

function decoratedConstructor(ctor) {
  return function(...args) {
    // Decoration logic
    return new ctor(...args);
  };
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱: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 ☕.