The relationship between the Prototype pattern and the JavaScript prototype chain
The Prototype Pattern is a creational design pattern that creates new objects by copying existing ones, rather than through constructors. JavaScript's prototype chain mechanism inherently supports this pattern, with deep conceptual and implementation-level connections between the two. Understanding this relationship enables more efficient use of JavaScript's features for object creation and inheritance.
Core Idea of the Prototype Pattern
The essence of the Prototype Pattern lies in creating new instances by cloning existing objects, avoiding repetitive initialization processes. In traditional object-oriented languages, this typically requires implementing a Cloneable
interface, whereas JavaScript builds this capability directly into its prototype chain. For example:
const carPrototype = {
wheels: 4,
drive() {
console.log('Driving with ' + this.wheels + ' wheels');
},
clone() {
return Object.create(this);
}
};
const myCar = carPrototype.clone();
myCar.drive(); // Output: Driving with 4 wheels
This pattern is particularly suitable for the following scenarios:
- When object initialization is costly (e.g., requiring complex calculations or I/O operations)
- When the system needs to dynamically choose which class to instantiate
- When avoiding the construction of class hierarchies is desirable
How JavaScript's Prototype Chain Works
Every JavaScript object has a __proto__
property (ES6 recommends using Object.getPrototypeOf
), forming the prototype chain lookup mechanism:
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return `Hello, ${this.name}`;
};
const john = new Person('John');
console.log(john.__proto__ === Person.prototype); // true
console.log(john.__proto__.__proto__ === Object.prototype); // true
The workflow of the prototype chain:
- When accessing an object property, first search the instance itself
- If not found, search up the
__proto__
chain - Continue until reaching
Object.prototype
(the top of the prototype chain)
Intersection of the Two Prototypes
The Prototype Pattern's implementation in JavaScript directly leverages the language's built-in prototype mechanism:
// Prototype Pattern implementation
const robotPrototype = {
beep() {
console.log(this.sound || 'Beep beep');
}
};
function createRobot(sound) {
const robot = Object.create(robotPrototype);
robot.sound = sound;
return robot;
}
const r2d2 = createRobot('Beep boop');
r2d2.beep(); // Output: Beep boop
Differences from the traditional Prototype Pattern:
- JavaScript handles delegation automatically via the prototype chain
- No need to explicitly implement a cloning interface
- Modifying the prototype affects all derived instances
Practical Application Patterns
Performance Optimization Scenarios
For scenarios requiring the creation of numerous similar objects:
// Bullet objects in a game
const bulletPrototype = {
x: 0,
y: 0,
speed: 10,
draw() {
console.log(`Drawing bullet at (${this.x}, ${this.y})`);
}
};
function createBullet(x, y) {
const bullet = Object.create(bulletPrototype);
bullet.x = x;
bullet.y = y;
return bullet;
}
// Create 100 bullets
const bullets = Array.from({length: 100}, (_, i) =>
createBullet(i % 10 * 50, Math.floor(i / 10) * 50)
);
Dynamic Inheritance Implementation
Modifying inheritance relationships at runtime using the prototype chain:
const animal = {
breathe() {
console.log('Breathing...');
}
};
const dog = Object.create(animal);
dog.bark = function() {
console.log('Woof!');
};
// Modify prototype at runtime
const superDog = Object.create(dog);
superDog.fly = function() {
console.log('Flying!');
};
superDog.breathe(); // Breathing...
superDog.bark(); // Woof!
superDog.fly(); // Flying!
Advanced Application Techniques
Prototype Property Shadowing
When prototypes and instances share property names:
const proto = { value: 42 };
const obj = Object.create(proto);
console.log(obj.value); // 42
obj.value = 100;
console.log(obj.value); // 100 (shadows the prototype property)
console.log(proto.value); // 42 (unchanged)
delete obj.value;
console.log(obj.value); // 42 (reverts to prototype property)
Prototype Chain Pollution Prevention
Preventing accidental modifications to the prototype chain:
// Safe prototype inheritance
function safeExtend(parent, child) {
const proto = Object.create(parent.prototype || Object.prototype);
// Copy enumerable properties
for (const key in child) {
if (child.hasOwnProperty(key)) {
proto[key] = child[key];
}
}
return proto;
}
function Parent() {}
Parent.prototype.method = function() {};
function Child() {}
Child.prototype = safeExtend(Parent, {
newMethod() {}
});
Evolution in Modern JavaScript
ES6's class syntax sugar is still based on the prototype chain:
class Vehicle {
constructor(wheels) {
this.wheels = wheels;
}
drive() {
console.log(`Driving with ${this.wheels} wheels`);
}
}
class Car extends Vehicle {
constructor() {
super(4);
}
}
console.log(Car.prototype.__proto__ === Vehicle.prototype); // true
The polyfill implementation of Object.create
reveals the nature of the prototype chain:
if (!Object.create) {
Object.create = function(proto) {
function F() {}
F.prototype = proto;
return new F();
};
}
Performance Considerations and Pitfalls
Excessively long prototype chains can degrade lookup performance:
// Create a deep prototype chain
let current = {};
for (let i = 0; i < 100; i++) {
current = Object.create(current);
current['level'+i] = i;
}
// Property lookup time increases with depth
console.time('deep lookup');
current.level99;
console.timeEnd('deep lookup'); // Several times slower than direct access
for...in
iterates over prototype chain properties:
const obj = Object.create({ inherited: true });
obj.own = true;
for (const key in obj) {
console.log(key); // Outputs 'own' and 'inherited'
if (obj.hasOwnProperty(key)) {
console.log('Own property:', key);
}
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn