红宝书第十讲:「构造函数与原型链」入门及深入解读:用举例子+图画理解“套娃继承”
资料取自《JavaScript高级程序设计(第5版)》。
查看总目录:红宝书学习大纲
一、构造函数:批量生产对象的“模板”
想象你开了一家宠物店🏪,需要批量创建小狗对象。构造函数就是你的生产模具,帮你快速生成小狗:
// 小狗制造机器(构造函数)
function Dog(name) {
this.name = name; // 每只小狗有个名字
this.bark = function() {
console.log("汪汪!");
};
}
// 生产两只小狗
const dog1 = new Dog("小黑");
const dog2 = new Dog("小白");
dog1.bark(); // "汪汪!"
dog2.bark(); // "汪汪!"
⚠️ 缺点:所有小狗都独立复制相同方法(如下图内存浪费)
⚠️ 构造函数导致内存浪费的解释与示意图
1. 根本问题:每个实例独立复制方法
当在构造函数内部直接定义方法时(非原型方法),每个实例都会创建一个新函数副本1。例如:
function Dog(name) {
this.name = name;
this.bark = function() { // ❌ 错误写法:每个方法都是新创建的
console.log("汪汪!");
};
}
const dog1 = new Dog("小黑");
const dog2 = new Dog("小白");
// 验证方法是否独立
console.log(dog1.bark === dog2.bark); // false 💡说明两个方法不同
2. 内存浪费图示
- 每只小狗的
bark
方法都是独立的
→ 1万只小狗就复制1万次相同的方法
→ 内存随着实例数量线性增长1 对比正确的原型方法(共享同一函数):
Dog.prototype.bark = function() { console.log("汪汪!"); }; console.log(dog1.bark === dog2.bark); // true ✅
3. 验证内存消耗(模拟代码)
// 测试构造函数增加内存占用
function Test() {
this.arr = new Array(1000000); // 模拟内存占用1MB
this.method = function() {}; // 每个实例额外占用内存
}
const instances = [];
for (let i=0; i<10; i++) {
instances.push(new Test()); // 总内存 ≈ 10MB实例 + 10MB方法
}
// 使用原型后,方法仅占用一次内存:
Test.prototype.method = function() {};
// 总内存 ≈ 10MB实例 + 1MB方法
二、原型(prototype):共享方法的“公共仓库”
所有构造函数都有个免费仓库(prototype
属性),存放公共方法,所有小狗共享这里的方法:
Dog.prototype.bark = function() {
console.log("汪汪!");
};
// 创建新小狗后,方法从仓库取
const dog3 = new Dog("小黄");
dog3.bark(); // 调用的是公共方法!
✅ 优点:所有小狗共用同一份方法,节省内存!
(修改Dog.prototype
,所有小狗会自动更新方法)
🔧 修改示例:
Dog.prototype.slogan = "我超可爱!";
console.log(dog3.slogan); // "我超可爱!" ✅
三、原型链:家族继承的“套娃规则”
想让小狗继承动物特征(如呼吸方法)怎么办?用原型链实现继承:
先定义一个动物:
function Animal() { this.breathe = function() { console.log("我在呼吸~"); }; }
让小狗的原型指向一个动物实例:
Dog.prototype = new Animal(); // 关键!小狗的仓库变成动物实例 Dog.prototype.constructor = Dog; // ✂️修复合库的标签
结果:所有小狗能调用动物的方法!
const dog4 = new Dog("小花"); dog4.breathe(); // "我在呼吸~" ✅ dog4.bark(); // "汪汪!" ✅
四、图解原型链:看看小狗的“祖宗十八代”
逐级查找过程:
- 小狗先找自己 →
dog.name
(自己身上有) - 再找Dog原型仓库 →
dog.bark()
(仓库里有) - 接着找Animal原型 →
dog.breathe()
(继承动物) - 最后到Object原型 →
dog.toString()
(所有对象默认方法)
💡 验证方法:
console.log(dog4 instanceof Dog); // true ✅
console.log(dog4 instanceof Animal); // true ✅
总结口诀
- 构造函数造实例
用new
生产对象,自带个性化属性。 - 原型对象是仓库
存公共方法,所有实例一起省内存。 - 套娃继承原型链
子类连父类,祖传方法随便用!
构造函数与原型链的深入详解:理解JavaScript的“血缘继承”
1. 构造函数:创建对象的“模具”
构造函数是一种特殊的函数,用于初始化对象。通过new
操作符调用构造函数,可以创建对象实例2:
function Person(name) {
this.name = name; // 实例属性
}
const alice = new Person("Alice");
console.log(alice.name); // "Alice"
- 关键作用:添加实例属性(如
name
) - 特性:每个实例独立拥有构造函数中定义的属性
2. 原型:共享方法的“公共蓝图”
每个函数(包括构造函数)都有一个prototype
属性,指向一个原型对象。原型上的属性和方法被所有实例共享3:
Person.prototype.sayHello = function() {
console.log(`你好,我是${this.name}`);
};
alice.sayHello(); // "你好,我是Alice"
const bob = new Person("Bob");
bob.sayHello(); // "你好,我是Bob"
原型结构图:Person.prototype是alice和bob共享的方法库
原型结构图详解:呈现对象间的“血脉关系”
1. 基础关系图(以Person
为例)
根据参考资料的描述45,构造函数、原型对象和实例的关系可通过以下Mermaid图表示:
关键说明:
- 构造函数(Person):通过
prototype
属性指向原型对象(Person.prototype)5。 - 原型对象(Person.prototype):通过
constructor
属性反向指向构造函数5。 - 实例(person1/person2):通过内部的
__proto__
指针指向原型对象4。
2. 完整的原型链结构
原型链的最终端是Object.prototype
,形成链式继承关系5:
逐级解释:
- 实例继承Person原型:
person1.__proto__
→Person.prototype
(包含共享方法)。 - Person原型继承Object原型:
Person.prototype.__proto__
→Object.prototype
(基础方法如toString
)。 - 终点为null:
Object.prototype.__proto__
→null
5。
3. 代码验证原型链5
可通过以下代码逐级检查原型链:
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
3. 原型链:实现继承的“家族树”
原型链的核心是让子类的原型指向父类的实例,从而继承父类的属性和方法1。
实现步骤:
- 定义父类构造函数和原型方法
- 子类构造函数通过
prototype
继承父类实例 修正子类的
constructor
指向function Animal(name) { this.name = name; } Animal.prototype.eat = function() { console.log(`${this.name}在吃东西`); }; // Dog继承Animal function Dog(name, breed) { Animal.call(this, name); // 调用父类构造函数 this.breed = breed; } Dog.prototype = new Animal(); // 关键步骤:设置原型链 Dog.prototype.constructor = Dog; // 修正constructor指向 Dog.prototype.bark = function() { console.log("汪汪!"); }; const myDog = new Dog("小黑", "哈士奇"); myDog.eat(); // 调用父类方法 → "小黑在吃东西" myDog.bark(); // 自己的方法 → "汪汪!"
4. 核心概念关系图
实例(myDog)
│
├── __proto__ → Dog.prototype
│ ├── constructor → Dog
│ ├── bark()
│ └── __proto__ → Animal.prototype
│ ├── eat()
│ └── __proto__ → Object.prototype
构造器关系:
Dog.prototype = new Animal() → 形成原型链
5. 实际应用与常见误区
典型场景:避免重复定义方法(如所有Dog
共享bark
方法)
误区示例:直接修改原型为对象会切断继承链
// ❌ 错误写法:直接覆盖原型
Dog.prototype = {
bark() { /* ... */ }
};
// 导致原型链断开,无法继承Animal的方法
// ✅ 正确写法:追加方法保留继承链
Dog.prototype.bark = function() { /* ... */ };
目录:总目录
上篇文章:红宝书第九讲:JavaScript对象创建与属性描述符详解
下篇文章:红宝书第十一讲:超易懂版「ES6类与继承」零基础教程:用现实例子+图解实现
脚注
- 原型链通过子类原型指向父类实例实现继承。来源:《JavaScript高级程序设计(第5版)》原型链代码示例部分。 ↩
- 构造函数通过
new
创建对象实例。来源:《JavaScript高级程序设计(第5版)》Basic Reference Types章节的构造函数说明。 ↩ - 原型模式通过
prototype
共享方法。来源:《JavaScript高级程序设计(第5版)》The Prototype Pattern小节。 ↩ - 原型通过实例的
__proto__
指向构造函数的prototype
属性。来源:《JavaScript高级程序设计(第5版)》Figure 8.1描述部分。 ↩ - 构造函数、原型和实例的交互关系示范及原型链终止于null的逻辑。来源:《JavaScript高级程序设计(第5版)》构造函数的原型关系代码验证部分。 ↩
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。