一、为什么需要原型?
1.1 传统构造函数的性能问题
// 问题示例:每次实例化都会创建新的方法
function Car(color) {
this.color = color;
// ❌ 不推荐:每个实例都会创建新的 drive 方法
this.drive = function() {
console.log('driving ' + this.color + ' car');
};
}
const car1 = new Car('red');
const car2 = new Car('blue');
console.log(car1.drive === car2.drive); // false - 不同内存地址
问题分析:每次实例化都会创建新的函数,造成内存浪费。
1.2 原型的解决方案
// ✅ 推荐:方法定义在原型上,所有实例共享
function Car(color) {
this.color = color; // 实例特有属性
}
// 共享的属性和方法放在原型上
Car.prototype = {
constructor: Car, // 修复构造函数指向
name: 'su7',
height: 1.4,
weight: 1.5,
drive() {
console.log(this.color + ' ' + this.name + ' is driving');
}
};
const car1 = new Car('red');
const car2 = new Car('blue');
console.log(car1.drive === car2.drive); // true - 共享同一个方法
二、原型链的核心概念
2.1 构造函数、原型、实例的三者关系
function Person(name, age) {
// 实例特有属性
this.name = name;
this.age = age;
}
// 原型对象 - 存储共享属性和方法
Person.prototype = {
constructor: Person, // 重要:修复构造函数引用
species: '人类',
introduce() {
return `我叫${this.name},今年${this.age}岁`;
}
};
const person1 = new Person('张三', 25);
// 🔍 验证原型关系
console.log('1. 实例的原型指向构造函数的原型:',
person1.__proto__ === Person.prototype); // true
console.log('2. 原型的构造函数指向构造函数本身:',
Person.prototype.constructor === Person); // true
console.log('3. 实例的构造函数:',
person1.constructor === Person); // true
console.log('4. 使用 Object.getPrototypeOf 获取原型:',
Object.getPrototypeOf(person1) === Person.prototype); // true
2.2 属性查找机制
function Person(name) {
this.name = name;
}
Person.prototype.species = '人类';
const p1 = new Person('李四');
console.log('=== 属性查找演示 ===');
// 1. 查找实例自身属性
console.log('实例自身属性 name:', p1.name); // '李四'
// 2. 查找原型链上的属性
console.log('原型链属性 species:', p1.species); // '人类'
// 3. 属性遮蔽(Property Shadowing)
p1.species = '智人'; // 在实例上创建同名属性
console.log('实例属性遮蔽原型属性:', p1.species); // '智人'
console.log('原型属性保持不变:', p1.__proto__.species); // '人类'
// 4. 删除实例属性后,重新访问原型属性
delete p1.species;
console.log('删除实例属性后:', p1.species); // '人类'
查找顺序:实例自身 → 原型对象 → 原型链上层 → ... → null
三、完整的原型链
3.1 原型链的顶端
function Person(name) {
this.name = name;
}
const person = new Person('王五');
console.log('=== 完整的原型链 ===');
// 1. 实例的原型指向构造函数的原型
console.log('person.__proto__ === Person.prototype:',
person.__proto__ === Person.prototype); // true
// 2. Person.prototype 的原型指向 Object.prototype
console.log('Person.prototype.__proto__ === Object.prototype:',
Person.prototype.__proto__ === Object.prototype); // true
// 3. Object.prototype 是原型链的顶端
console.log('Object.prototype.__proto__:',
Object.prototype.__proto__); // null
// 4. 方法继承演示
console.log('person.toString():', person.toString()); // [object Object]
console.log('toString方法来源:',
person.toString === Object.prototype.toString); // true
3.2 对象字面量的原型关系
// 对象字面量实际上是 new Object() 的语法糖
const obj = {
name: '张三',
age: 18
};
// 等价于
const obj2 = new Object();
obj2.name = '张三';
obj2.age = 18;
console.log('对象字面量的原型:', obj.__proto__ === Object.prototype); // true
console.log('对象的方法继承:', obj.toString === Object.prototype.toString); // true
四、原型链继承
4.1 实现简单的原型继承
console.log('=== 原型链继承演示 ===');
// 基础对象
const animalProto = {
species: '动物',
breathe() {
console.log(this.name + ' is breathing');
}
};
// 构造函数
function Animal(name) {
this.name = name;
}
// 设置原型
Animal.prototype = animalProto;
function Person(name, age) {
// 绑定this指向将来通过new Person()创建的实例对象
// 借用父类构造函数来初始化子类实例的属性
// 把Animal函数运行一遍,并且强制让里面的this等于我传进去的这个this(也就是person实例)
// 相当于调用父类方法,避免重复书写代码
Animal.call(this, name);
this.age = age;
}
// 设置 Person 的原型链:继承 Animal.prototype
// 用Object.create()的目的是让Person.prototype继承Animal.prototype,但又不直接等于它
// 这样Person.prototype的__proto__指向Animal.prototype,形成原型链
// 如果直接写Person.prototype = Animal.prototype,那么修改Person.prototype会污染Animal.prototype!
Person.prototype = Object.create(Animal.prototype);
Person.prototype.constructor = Person; // 修复构造函数
// 添加子类特有方法
Person.prototype.speak = function() {
console.log(`我是${this.name},今年${this.age}岁`);
};
const person = new Person('赵六', 30);
// 验证继承关系
person.breathe(); // 继承自 Animal.prototype
person.speak(); // Person 自有方法
console.log('物种:', person.species); // 继承自 Animal.prototype
上述代码形成了类似这样的原型链:
person
→ __proto__ → Person.prototype
→ __proto__ → Animal.prototype (== animalProto)
→ __proto__ → Object.prototype
4.2 更清晰的原型链示例
// 清晰的继承层次
function Animal(species) {
this.species = species || '未知物种';
}
Animal.prototype.getSpecies = function() {
return this.species;
};
function Mammal(name) {
Animal.call(this, '哺乳动物');
this.name = name;
}
// 建立原型链
Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.constructor = Mammal;
Mammal.prototype.giveBirth = function() {
console.log(this.name + ' is giving birth to live young');
};
function Human(name, age) {
Mammal.call(this, name);
this.age = age;
}
Human.prototype = Object.create(Mammal.prototype);
Human.prototype.constructor = Human;
Human.prototype.think = function() {
console.log(this.name + ' is thinking deeply');
};
// 使用示例
const human = new Human('小明', 25);
console.log('=== 原型链方法调用 ===');
human.getSpecies(); // 来自 Animal
human.giveBirth(); // 来自 Mammal
human.think(); // 来自 Human
console.log('=== 原型链验证 ===');
console.log('human instanceof Human:', human instanceof Human); // true
console.log('human instanceof Mammal:', human instanceof Mammal); // true
console.log('human instanceof Animal:', human instanceof Animal); // true
上述代码形成了类似这样的原型链:
human
→ __proto__ → Human.prototype
→ __proto__ → Mammal.prototype
→ __proto__ → Animal.prototype
→ __proto__ → Object.prototype
→ __proto__ → null
五、重要注意事项
5.1 原型重写的问题
function Person(name) {
this.name = name;
}
// ❌ 问题:重写 prototype 会丢失 constructor
Person.prototype = {
sayHi() {
console.log('Hi, ' + this.name);
}
};
const p1 = new Person('张三');
console.log('constructor 丢失:', p1.constructor === Person); // false
console.log('constructor 指向:', p1.constructor); // [Function: Object]
// ✅ 解决方案:手动修复 constructor
Person.prototype = {
constructor: Person, // 重要:修复构造函数引用
sayHi() {
console.log('Hi, ' + this.name);
}
};
const p2 = new Person('李四');
console.log('constructor 修复后:', p2.constructor === Person); // true
5.2 原型方法的 this 指向
function Person(name) {
this.name = name;
}
Person.prototype = {
constructor: Person,
name: '原型上的name', // 会被实例属性遮蔽
introduce() {
// this 指向调用该方法的实例
console.log('我是' + (this.name || '无名氏'));
}
};
const person = new Person('实例name');
person.introduce(); // '我是实例name'
// 直接调用原型方法时的 this 指向问题
const introFunc = person.introduce;
// introFunc(); // 在严格模式下会报错,非严格模式下 this 指向全局对象
六、现代 JavaScript 中的原型
6.1 ES6 Class 语法糖
// ES6 Class 本质还是原型继承
class Car {
constructor(color) {
this.color = color;
}
// 方法自动添加到原型上
drive() {
console.log(this.color + ' car is driving');
}
// 静态方法
// 静态方法不能被实例调用,只能通过构造函数(或类)直接调用
static info() {
return 'This is a Car class';
}
}
// 等同于
function OldCar(color) {
this.color = color;
}
OldCar.prototype.drive = function() {
console.log(this.color + ' car is driving');
};
OldCar.info = function() {
return 'This is a Car class';
};
const car1 = new Car('red');
const car2 = new OldCar('blue');
console.log('Class 方法在原型上:',
car1.drive === Car.prototype.drive); // true
console.log('传统写法方法在原型上:',
car2.drive === OldCar.prototype.drive); // true
总结
核心要点:
- 性能优化:将共享方法放在原型上,避免重复创建
- 原型三角:构造函数、原型对象、实例之间的引用关系
- 查找机制:属性查找沿着原型链自下而上
- 继承实现:通过原型链实现对象之间的继承关系
- constructor修复:重写 prototype 时需要手动修复 constructor
最佳实践:
- 实例特有属性定义在构造函数中
- 共享方法和属性定义在原型上
- 使用
Object.create()建立原型链 - 重写 prototype 时记得修复 constructor
- 理解 ES6 class 只是语法糖,底层仍是原型机制
掌握原型和原型链是深入理解 JavaScript 面向对象编程的关键,这些概念在现代框架和库开发中仍然发挥着重要作用。