The difference between the Constructor pattern and traditional classes
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:
- Constructor names typically start with a capital letter.
- Instance properties are dynamically bound via
this
. - Methods must be explicitly defined on
prototype
. - 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:
- Methods are defined directly within the class body.
- The constructor uses the fixed name
constructor
. - Supports
static
methods. - 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 accessingthis
. - 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:
- Trigger hidden class changes when modifying prototypes.
- Affect inline caching when dynamically adding methods.
- 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