红宝书第十讲:「构造函数与原型链」入门及深入解读:用举例子+图画理解“套娃继承”

红宝书第十讲:「构造函数与原型链」入门及深入解读:用举例子+图画理解"套娃继承"

资料取自《JavaScript高级程序设计(第5版)》。 查看总目录:红宝书学习大纲


一、构造函数:批量生产对象的"模板"

想象你开了一家宠物店🏪,需要批量创建小狗对象。构造函数就是你的生产模具,帮你快速生成小狗:

javascript 复制代码
// 小狗制造机器(构造函数)
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^。例如:

javascript 复制代码
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. 内存浪费图示

flowchart LR dog1["dog1实例 name: '小黑' bark: 方法副本1"] dog2["dog2实例 name: '小白' bark: 方法副本2"] dogN["...更多实例 bark: 方法副本N"] 内存占用 --> dog1 内存占用 --> dog2 内存占用 --> dogN
  • 每只小狗的bark方法都是独立的

    → 1万只小狗就复制1万次相同的方法

    → 内存随着实例数量线性增长^1^

  • 对比正确的原型方法(共享同一函数):

    javascript 复制代码
    Dog.prototype.bark = function() { 
      console.log("汪汪!");
    };
    console.log(dog1.bark === dog2.bark); // true ✅

3. 验证内存消耗(模拟代码)

javascript 复制代码
// 测试构造函数增加内存占用
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属性),存放公共方法,所有小狗共享这里的方法:

javascript 复制代码
Dog.prototype.bark = function() { 
  console.log("汪汪!"); 
};

// 创建新小狗后,方法从仓库取
const dog3 = new Dog("小黄");
dog3.bark(); // 调用的是公共方法!

优点 :所有小狗共用同一份方法,节省内存!

(修改Dog.prototype,所有小狗会自动更新方法)

🔧 修改示例

javascript 复制代码
Dog.prototype.slogan = "我超可爱!";
console.log(dog3.slogan); // "我超可爱!" ✅

三、原型链:家族继承的"套娃规则"

想让小狗继承动物特征(如呼吸方法)怎么办?用原型链实现继承:

  1. 先定义一个动物

    javascript 复制代码
    function Animal() {
      this.breathe = function() {
        console.log("我在呼吸~");
      };
    }
  2. 让小狗的原型指向一个动物实例

    javascript 复制代码
    Dog.prototype = new Animal(); // 关键!小狗的仓库变成动物实例
    Dog.prototype.constructor = Dog; // ✂️修复合库的标签
  3. 结果:所有小狗能调用动物的方法!

    javascript 复制代码
    const dog4 = new Dog("小花");
    dog4.breathe(); // "我在呼吸~" ✅
    dog4.bark();    // "汪汪!" ✅

四、图解原型链:看看小狗的"祖宗十八代"

flowchart LR dog["小狗 dog4"] dogProto["Dog.prototype(动物实例)"] animalProto["Animal.prototype"] objectProto["Object.prototype"] nullNode["null"] dog -->|__proto__| dogProto dogProto -->|__proto__| animalProto animalProto -->|__proto__| objectProto objectProto -->|__proto__| nullNode

逐级查找过程

  1. 小狗先找自己 → dog.name(自己身上有)
  2. 再找Dog原型仓库 → dog.bark()(仓库里有)
  3. 接着找Animal原型 → dog.breathe()(继承动物)
  4. 最后到Object原型 → dog.toString()(所有对象默认方法)

💡 验证方法

javascript 复制代码
console.log(dog4 instanceof Dog);    // true ✅
console.log(dog4 instanceof Animal); // true ✅

总结口诀

  1. 构造函数造实例
    new生产对象,自带个性化属性。
  2. 原型对象是仓库
    存公共方法,所有实例一起省内存。
  3. 套娃继承原型链
    子类连父类,祖传方法随便用!

构造函数与原型链的深入详解:理解JavaScript的"血缘继承"


1. 构造函数:创建对象的"模具"

构造函数是一种特殊的函数,用于初始化对象。通过new操作符调用构造函数,可以创建对象实例^2^:

javascript 复制代码
function Person(name) {
  this.name = name; // 实例属性
}

const alice = new Person("Alice");
console.log(alice.name); // "Alice"
  • 关键作用 :添加实例属性(如name
  • 特性:每个实例独立拥有构造函数中定义的属性

2. 原型:共享方法的"公共蓝图"

每个函数(包括构造函数)都有一个prototype属性,指向一个原型对象。原型上的属性和方法被所有实例共享 ^3^:

javascript 复制代码
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为例)

根据参考资料的描述^4^^5^,构造函数、原型对象和实例的关系可通过以下Mermaid图表示:

flowchart LR subgraph 构造函数 Person["Person 构造函数"] end subgraph 原型对象 proto["Person.prototype"] proto -->|constructor| Person end subgraph 实例 person1["person1 实例"] person2["person2 实例"] end Person -->|prototype 属性| proto person1 -->|__proto__ 指针| proto person2 -->|__proto__ 指针| proto

关键说明

  • 构造函数(Person) :通过 prototype 属性指向原型对象(Person.prototype)^5^。
  • 原型对象(Person.prototype) :通过 constructor 属性反向指向构造函数^5^。
  • 实例(person1/person2) :通过内部的 __proto__ 指针指向原型对象^4^。

2. 完整的原型链结构

原型链的最终端是Object.prototype,形成链式继承关系^5^:

flowchart TB person["实例 person1"] PersonProto["Person.prototype"] ObjectProto["Object.prototype"] nullNode["null"] person -->|__proto__| PersonProto PersonProto -->|__proto__| ObjectProto ObjectProto -->|__proto__| nullNode

逐级解释

  1. 实例继承Person原型person1.__proto__Person.prototype(包含共享方法)。
  2. Person原型继承Object原型Person.prototype.__proto__Object.prototype(基础方法如toString)。
  3. 终点为nullObject.prototype.__proto__null^5^。

3. 代码验证原型链 ^[5](#3. 代码验证原型链5 "#user-content-fn-4")^

可通过以下代码逐级检查原型链:

javascript 复制代码
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

3. 原型链:实现继承的"家族树"

原型链的核心是让子类的原型指向父类的实例 ,从而继承父类的属性和方法^1^。
实现步骤

  1. 定义父类构造函数和原型方法
  2. 子类构造函数通过prototype继承父类实例
  3. 修正子类的constructor指向
javascript 复制代码
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. 核心概念关系图

plaintext 复制代码
实例(myDog) 
  │
  ├── __proto__ → Dog.prototype
  │               ├── constructor → Dog
  │               ├── bark() 
  │               └── __proto__ → Animal.prototype
  │                                  ├── eat()
  │                                  └── __proto__ → Object.prototype
构造器关系:
Dog.prototype = new Animal() → 形成原型链

5. 实际应用与常见误区

典型场景 :避免重复定义方法(如所有Dog共享bark方法) 误区示例:直接修改原型为对象会切断继承链

javascript 复制代码
// ❌ 错误写法:直接覆盖原型
Dog.prototype = {
  bark() { /* ... */ }
};
// 导致原型链断开,无法继承Animal的方法

// ✅ 正确写法:追加方法保留继承链
Dog.prototype.bark = function() { /* ... */ };

目录:总目录 上篇文章:红宝书第九讲:JavaScript对象创建与属性描述符详解

脚注

Footnotes

  1. 原型链通过子类原型指向父类实例实现继承。来源:《JavaScript高级程序设计(第5版)》原型链代码示例部分。 2 3

  2. 构造函数通过new创建对象实例。来源:《JavaScript高级程序设计(第5版)》Basic Reference Types章节的构造函数说明。

  3. 原型模式通过prototype共享方法。来源:《JavaScript高级程序设计(第5版)》The Prototype Pattern小节。

  4. 原型通过实例的__proto__指向构造函数的prototype属性。来源:《JavaScript高级程序设计(第5版)》Figure 8.1描述部分。 2

  5. 构造函数、原型和实例的交互关系示范及原型链终止于null的逻辑。来源:《JavaScript高级程序设计(第5版)》构造函数的原型关系代码验证部分。 2 3 4 5 6

相关推荐
聪明的墨菲特i3 分钟前
React与Vue:哪个框架更适合入门?
开发语言·前端·javascript·vue.js·react.js
时光少年4 分钟前
Android 副屏录制方案
android·前端
拉不动的猪11 分钟前
v2升级v3需要兼顾的几个方面
前端·javascript·面试
时光少年14 分钟前
Android 局域网NIO案例实践
android·前端
半兽先生29 分钟前
VueDOMPurifyHTML 防止 XSS(跨站脚本攻击) 风险
前端·xss
冴羽32 分钟前
SvelteKit 最新中文文档教程(20)—— 最佳实践之性能
前端·javascript·svelte
Nuyoah.33 分钟前
《Vue3学习手记2》
javascript·vue.js·学习
Jackson__39 分钟前
面试官:谈一下在 ts 中你对 any 和 unknow 的理解
前端·typescript
zpjing~.~1 小时前
css 二维码始终显示在按钮的正下方,并且根据不同的屏幕分辨率自动调整位置
前端·javascript·html
红虾程序员1 小时前
Linux进阶命令
linux·服务器·前端