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

资料取自《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. 内存浪费图示

flowchart LR
    dog1["dog1实例 
      name: '小黑'
      bark: 方法副本1"]
    dog2["dog2实例 
      name: '小白'
      bark: 方法副本2"]
    dogN["...更多实例
      bark: 方法副本N"]

    内存占用 --> dog1
    内存占用 --> dog2
    内存占用 --> dogN
  • 每只小狗的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); // "我超可爱!" ✅

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

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

  1. 先定义一个动物

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

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

    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()(所有对象默认方法)

💡 验证方法

console.log(dog4 instanceof Dog);    // true ✅
console.log(dog4 instanceof Animal); // true ✅

总结口诀

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

构造函数与原型链的深入详解:理解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图表示:

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__null5

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
实现步骤

  1. 定义父类构造函数和原型方法
  2. 子类构造函数通过prototype继承父类实例
  3. 修正子类的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类与继承」零基础教程:用现实例子+图解实现

脚注


  1. 原型链通过子类原型指向父类实例实现继承。来源:《JavaScript高级程序设计(第5版)》原型链代码示例部分。
  2. 构造函数通过new创建对象实例。来源:《JavaScript高级程序设计(第5版)》Basic Reference Types章节的构造函数说明。
  3. 原型模式通过prototype共享方法。来源:《JavaScript高级程序设计(第5版)》The Prototype Pattern小节。
  4. 原型通过实例的__proto__指向构造函数的prototype属性。来源:《JavaScript高级程序设计(第5版)》Figure 8.1描述部分。
  5. 构造函数、原型和实例的交互关系示范及原型链终止于null的逻辑。来源:《JavaScript高级程序设计(第5版)》构造函数的原型关系代码验证部分。

kovli
7 声望4 粉丝