"不再迷惑!用'血缘关系'彻底搞懂JavaScript原型链机制"

JavaScript原型链深度理解 - 完整版

1. 核心概念理解

1.1 普通函数和构造函数

在JS中无论是普通函数还是构造函数,他们的共同点都是函数对象。

不同点在于:

  • 普通函数:执行特定逻辑的函数对象
  • 构造函数 :一种特殊的函数对象,可以通过 new 关键字来实例化一个新的对象

在JS中的内置函数(通常大写字母开头,例如 FunctionNumberStringArrayObject 等),这些函数就是特殊的构造函数。

1.2 关键对象关系澄清

🔑 重要理解

  • 构造函数对象构造函数.prototype对象两个独立的并列对象
  • 它们之间不存在包含关系或从属关系,而是相互依附的并列关系
  • 构造函数.prototype 的存在意义:存储所有通过 new 构造函数() 创建的实例可以共享的方法和属性
csharp 复制代码
function Dog() {}
// Dog 是构造函数对象
// Dog.prototype 是另一个独立对象,专门为实例提供共享方法

2. Constructor属性的双重含义

2.1 JavaScript自动设置的constructor

当你创建一个函数时,JavaScript引擎会自动做两件事:

  1. 自动创建 函数.prototype 对象
  2. 在prototype对象上自动添加 constructor 属性,指向函数本身
csharp 复制代码
function myFunc() {}
// JavaScript自动创建:
// myFunc.prototype 对象
// myFunc.prototype.constructor = myFunc

2.2 Constructor的两个核心公式 🎯

公式1:对象.constructor === 该对象的构造函数本身
ini 复制代码
function Dog() {}
let dog = new Dog();
console.log(dog.constructor === Dog);        // true
console.log(Dog.constructor === Function);   // true
公式2:对象.prototype.constructor === 对象本身
javascript 复制代码
console.log(Dog.prototype.constructor === Dog);           // true
console.log(Function.prototype.constructor === Function); // true
console.log(Number.prototype.constructor === Number);     // true

2.3 拟人化理解:constructor就是"缔造者"标记 💡

第一个问答

ini 复制代码
let dog = new Dog();
dog.constructor  // dog问:"谁是我的缔造者?"
=== Dog          // 答案:"Dog构造函数是你的缔造者!"

第二个问答

javascript 复制代码
Dog.prototype.constructor  // Dog.prototype对象问:"谁是我的缔造者?"
=== Dog                   // 答案:"Dog本身是你的缔造者!"

深层含义

  • constructor不是随意的属性,而是血缘关系的标记
  • 每个对象都"记得"谁创造了它
  • 这种记忆形成了JavaScript对象世界的家族谱系

3. 原型链继承机制详解

3.1 Object.create() 的本质

ini 复制代码
// Object.create(Animal.prototype) 做了什么?
let newObj = Object.create(Animal.prototype);

// 等价于:
let newObj = {};
newObj.__proto__ = Animal.prototype;

关键理解

  • Object.create() 创建一个全新的空对象
  • 新对象自身没有任何方法和属性
  • 新对象的 __proto__ 指向传入的参数

3.2 继承设置的完整过程

javascript 复制代码
function Animal(name) {
    this.name = name;
}
Animal.prototype.speak = function() {
    console.log(`${this.name} makes a sound`);
};
Animal.prototype.smile = function() {
    console.log(`${this.name} smiles`);
};

function Dog(name, breed) {
    Animal.call(this, name); // 调用父类构造函数
    this.breed = breed;
}

// ⚠️ 关键步骤:建立原型链继承
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修正constructor指向

// 添加子类特有方法
Dog.prototype.bark = function() {
    console.log(`${this.name} says Woof!`);
};

// 测试
let dog = new Dog("Buddy", "Golden Retriever");
dog.speak(); // "Buddy makes a sound" (继承自Animal)
dog.smile(); // "Buddy smiles" (继承自Animal)
dog.bark();  // "Buddy says Woof!" (Dog特有方法)

3.3 为什么需要修正constructor

javascript 复制代码
function Dog() {}
console.log(Dog.prototype.constructor === Dog); // true (默认情况)

Dog.prototype = Object.create(Animal.prototype);
console.log(Dog.prototype.constructor === Dog); // false! 现在指向Animal
console.log(Dog.prototype.constructor === Animal); // true

Dog.prototype.constructor = Dog; // 修正回来
console.log(Dog.prototype.constructor === Dog); // true

constructor的实际用途

  • 通过实例的constructor创建同类型对象
  • 用于类型检查和调试
  • 在库和框架中被广泛使用
javascript 复制代码
let dog = new Dog("Buddy");
// 通过constructor创建同类型对象
let anotherDog = new dog.constructor("Max", "Labrador");
console.log(anotherDog instanceof Dog); // true

4. 常见陷阱与误区

4.1 赋值操作的陷阱 ⚠️

错误顺序(方法会丢失)

javascript 复制代码
function Dog() {}
// 1. 先添加方法
Dog.prototype.bark = function() {
    console.log("Woof!");
};
// 2. 后设置继承 - bark方法丢失!
Dog.prototype = Object.create(Animal.prototype);

let dog = new Dog();
dog.bark(); // ❌ 报错!方法不存在

为什么bark方法丢失

  1. Object.create(Animal.prototype) 创建了一个全新的空对象
  2. 新对象的 __proto__ 指向 Animal.prototype
  3. Dog.prototype = ...Dog.prototype 变量重新指向新对象的地址
  4. 原来的 Dog.prototype 对象(包含bark方法)失去引用,bark方法丢失

正确顺序

javascript 复制代码
function Dog() {}
// 1. 先建立继承关系
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// 2. 再添加子类方法
Dog.prototype.bark = function() {
    console.log("Woof!");
};

let dog = new Dog();
dog.speak(); // ✅ "Animal makes a sound"
dog.bark();  // ✅ "Woof!"

4.2 原型链查找机制

查找路径

javascript 复制代码
dog实例 → Dog.prototype → Animal.prototype → Object.prototype → null

实际运行过程

ini 复制代码
let dog = new Dog("Buddy");
dog.speak(); // 查找:dog → Dog.prototype(没有) → Animal.prototype(找到!)
dog.bark();  // 查找:dog → Dog.prototype(找到!)

5. JavaScript方法定义与Python方法对比

5.1 JavaScript构造函数方法的特点

在JavaScript中:

  • 构造函数的方法通过 构造函数名.prototype.方法名 = function(){} 来定义
  • 这些定义好的方法都统一存放构造函数名.prototype 对象中
  • 所有实例共享这些方法,避免重复创建
javascript 复制代码
function Dog(name) {
    this.name = name;
}

// 方法定义:存放在 Dog.prototype 中
Dog.prototype.bark = function() {
    console.log(`${this.name} says Woof!`);
};

Dog.prototype.run = function() {
    console.log(`${this.name} is running`);
};

// 所有实例共享这些方法
let dog1 = new Dog("Buddy");
let dog2 = new Dog("Max");
console.log(dog1.bark === dog2.bark); // true - 同一个方法

5.2 与Python方法的重要区别 ⚠️

重要澄清:JavaScript构造函数的方法 ≠ Python类方法

ruby 复制代码
# Python中的方法分类
class Dog:
    species = "Canis"  # 类属性
    
    @classmethod  # 类方法:通过类名调用,接收cls参数
    def get_species(cls):
        return cls.species
    
    @staticmethod  # 静态方法:不接收特殊参数
    def get_info():
        return "This is a dog class"
    
    def bark(self):  # 实例方法:通过实例调用,接收self参数
        print("Woof!")

# 调用方式对比
Dog.get_species()    # 类方法调用
Dog.get_info()       # 静态方法调用
dog = Dog()
dog.bark()           # 实例方法调用
javascript 复制代码
// JavaScript对应实现
function Dog(name) {
    this.name = name;
}

// JavaScript的prototype方法 ≈ Python的实例方法
Dog.prototype.bark = function() {  
    console.log(`${this.name} says Woof!`);
};

// 模拟Python类方法:直接在构造函数上定义
Dog.species = "Canis";
Dog.getSpecies = function() {  // 模拟类方法
    return this.species;
};

// 模拟静态方法
Dog.getInfo = function() {  // 模拟静态方法
    return "This is a dog class";
};

// 调用方式
Dog.getSpecies();  // 通过构造函数调用(模拟类方法)
Dog.getInfo();     // 通过构造函数调用(模拟静态方法)
let dog = new Dog("Buddy");
dog.bark();        // 通过实例调用(实例方法)

5.3 方法类型对应关系

Python方法类型 JavaScript实现方式 调用方式 特点
实例方法 def method(self) Constructor.prototype.method = function() instance.method() 通过实例调用,可访问实例属性
类方法 @classmethod def method(cls) Constructor.method = function() Constructor.method() 通过构造函数调用,JavaScript需手动实现
静态方法 @staticmethod def method() Constructor.method = function() Constructor.method() 通过构造函数调用,不依赖实例或类状态

5.4 关键理解要点 💡

  1. JavaScript的 prototype 方法 本质上是 实例方法,不是类方法

  2. JavaScript没有原生的类方法概念,需要手动在构造函数上定义来模拟

  3. 方法存储位置决定了调用方式

    • Constructor.prototype.method → 实例调用
    • Constructor.method → 构造函数调用

6. 与Python继承的对比

特性 JavaScript原型链 Python类继承
继承模型 基于原型的继承 基于类的继承
对象关系 构造函数与prototype并列依附 类与实例的层次关系
方法存储 Constructor.prototype.method 实例方法存储在类中
方法类型 主要是实例方法,需手动模拟类方法 原生支持实例方法、类方法、静态方法
查找机制 沿原型链向上查找 按MRO顺序查找
动态性 可运行时修改原型 相对静态
设置继承 Child.prototype = Object.create(Parent.prototype) class Child(Parent):
多重继承 不直接支持,需要混入模式 原生支持多重继承

相似之处

  • 都支持向上查找属性/方法
  • 子类/实例可以访问父类/原型的方法
  • 都支持方法重写

6. 核心记忆法则 💡

6.1 两大核心公式

  1. 对象.constructor === 该对象的构造函数本身 - 问"谁是我的缔造者?"
  2. 对象.prototype.constructor === 对象本身 - prototype问"谁缔造了我?"

6.2 五个关键要点

  1. 函数和prototype是两个独立的并列对象
  2. Object.create()创建全新对象,不是修改现有对象
  3. 赋值操作会替换整个对象引用
  4. 顺序很重要:先继承,后添加方法
  5. 记得修正constructor指向

6.3 家族谱系思维

kotlin 复制代码
// 血缘追溯链
dog.constructor           // "我的直接缔造者是Dog"
dog.constructor.constructor // "Dog的缔造者是Function"
// 最终都追溯到Function这个"始祖缔造者"

7. 原型链的深层结构分析

7.1 构造函数.prototype对象的原型链

重要发现 :构造函数的prototype对象也有自己的 __proto__ 属性!

javascript 复制代码
function Dog() {}

// Dog.prototype也是一个对象,所以它也有__proto__
console.log(Dog.prototype.__proto__ === Object.prototype); // true (默认情况)

7.2 两个核心原型链公式

公式1:对象.__proto__ === 构造该对象的构造函数.prototype
javascript 复制代码
function Dog() {}
let dog = new Dog();

console.log(dog.__proto__ === Dog.prototype); // true
console.log(Dog.__proto__ === Function.prototype); // true - 函数也是对象
公式2:构造函数.prototype.__proto__ === Object.prototype(默认情况)
javascript 复制代码
function Dog() {}
console.log(Dog.prototype.__proto__ === Object.prototype); // true

// 但在继承情况下会改变:
function Animal() {}
function Cat() {}
Cat.prototype = Object.create(Animal.prototype);
console.log(Cat.prototype.__proto__ === Animal.prototype); // true
console.log(Cat.prototype.__proto__ === Object.prototype); // false

7.3 完整的双重原型链结构

JavaScript中存在两条并行的继承链:

实例对象的原型链

csharp 复制代码
function Dog() {}
let dog = new Dog();

dog                     // 实例对象
→ dog.__proto__         // Dog.prototype  
→ dog.__proto__.__proto__ // Object.prototype
→ null                  // 链条终点

函数对象的原型链

csharp 复制代码
Dog                     // 函数对象
→ Dog.__proto__         // Function.prototype
→ Dog.__proto__.__proto__ // Object.prototype  
→ null                  // 链条终点

7.4 Function的特殊性 🔍

JavaScript中最特殊的情况Function.__proto__ === Function.prototype

javascript 复制代码
// 验证Function的特殊性
console.log(Function.__proto__ === Function.prototype); // true!

// 对比普通函数
function Dog() {}
console.log(Dog.__proto__ === Function.prototype);     // true
console.log(Dog.__proto__ === Dog.prototype);          // false

为什么Function如此特殊?

  • Function 构造函数是自己创建自己的特殊情况
  • 根据公式1,由于Function是由自己构造的,所以 Function.__proto__ === Function.prototype

7.5 继承问答的完整体系

每一层 __proto__ 都在回答同一个问题:"我的方法从哪里继承?"

javascript 复制代码
function Animal() {}
function Dog() {}
Dog.prototype = Object.create(Animal.prototype);
let dog = new Dog();

// 实例的继承问答链:
dog.__proto__                    // dog问:"我继承于哪?" → Dog.prototype
dog.__proto__.__proto__          // Dog.prototype问:"我继承于哪?" → Animal.prototype  
dog.__proto__.__proto__.__proto__ // Animal.prototype问:"我继承于哪?" → Object.prototype

// 函数的继承问答链:
Dog.__proto__           // Dog问:"我继承于哪?" → Function.prototype
Dog.__proto__.__proto__ // Function.prototype问:"我继承于哪?" → Object.prototype

7.6 常见误区澄清 ⚠️

误区1:认为所有对象都有prototype属性

javascript 复制代码
let dog = new Dog();
console.log(dog.prototype); // undefined - 实例对象没有prototype属性

误区2:混淆缔造者关系和原型链关系

javascript 复制代码
function Dog() {}

// 缔造者关系(constructor)- 问"谁创建了我?"
console.log(Dog.prototype.constructor === Dog); // true

// 原型链关系(__proto__)- 问"我的方法从哪里继承?"  
console.log(Dog.prototype.__proto__ === Object.prototype); // true

8. 实用调试技巧

javascript 复制代码
// 检查原型链
console.log(dog.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.__proto__ === Animal.prototype); // true

// 检查constructor(缔造者关系)
console.log(dog.constructor === Dog); // true
console.log(Dog.prototype.constructor === Dog); // true

// 检查继承关系
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true

// 查看完整原型链
console.log(dog.__proto__);                    // Dog.prototype
console.log(dog.__proto__.__proto__);          // Animal.prototype  
console.log(dog.__proto__.__proto__.__proto__); // Object.prototype

终极总结 :JavaScript原型链通过让子类的prototype对象的__proto__指向父类的prototype对象,实现了实例可以沿原型链访问父类方法的继承机制。constructor属性是对象世界的"血缘标记",记录着"谁是缔造者"的关系。关键是理解prototype对象的独立性、Object.create()的替换本质,以及操作顺序的重要性。

相关推荐
excel2 分钟前
一文读懂 Vue 组件间通信机制(含 Vue2 / Vue3 区别)
前端·javascript·vue.js
JarvanMo6 分钟前
Flutter 应用生命周期:使用 AppLifecycleListener 阻止应用崩溃
前端
我的xiaodoujiao1 小时前
从 0 到 1 搭建 Python 语言 Web UI自动化测试学习系列 9--基础知识 5--常用函数 3
前端·python·测试工具·ui
李鸿耀3 小时前
Flex 布局下文字省略不生效?原因其实很简单
前端
皮蛋瘦肉粥_1214 小时前
pink老师html5+css3day06
前端·css3·html5
华仔啊8 小时前
前端必看!12个JS神级简写技巧,代码效率直接飙升80%,告别加班!
前端·javascript
excel8 小时前
dep.ts 逐行解读
前端·javascript·vue.js
爱上妖精的尾巴8 小时前
5-20 WPS JS宏 every与some数组的[与或]迭代(数组的逻辑判断)
开发语言·前端·javascript·wps·js宏·jsa
excel9 小时前
Vue3 响应式核心源码全解析:Dep、Link 与 track/trigger 完整执行机制详解
前端
前端大卫9 小时前
一个关于时区的线上问题
前端·javascript·vue.js