JavaScript 原型与原型链:深入理解与实践

57 阅读2分钟

1. JavaScript 的原型基础

JavaScript 是一门**基于原型(Prototype)的面向对象语言,不同于 Java、C++ 等基于“类”的语言。在 JavaScript 中,对象之间的继承关系通过原型链(Prototype Chain)**来实现。

1.1 prototype 的本质

  • prototype函数对象的一个属性;
  • 它指向一个对象,这个对象就是“原型对象”;
  • 所有通过该函数创建的实例,都会共享这个原型对象上的属性和方法;
  • 当我们说 “prototype” 时,通常指的是它所指向的那个对象,而非属性本身——这也是“原型对象”名称的由来。
function Person(name, age) {
    this.name = name;
    this.age = age;
}

每个构造函数都自带一个 prototype 属性:

Person.prototype.species = '人类';
Person.prototype.sayHi = function() {
    console.log(`你好,我是${this.name}`);
};

4d89a5ea37ca4e5797b09c1d7ed836fb.png

为什么要把公共方法定义在 prototype 上?

对比下面两种写法:

✅ 推荐方式(定义在 prototype 上):

Person.prototype.sayHi = function() { ... };

❌ 不推荐方式(定义在构造函数内部):

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayHi = function() { ... }; // 每个实例都会创建一份副本
}

优点:将公共方法定义在 prototype 上,可以让所有实例共享同一个方法引用,避免重复创建,大幅节省内存。

1.2 实例的 __proto__ 属性

使用 new 调用构造函数创建实例时,实例会自动拥有一个内部属性 __proto__(规范中称为 [[Prototype]]),它指向其构造函数的 prototype 对象。

const person1 = new Person('张三', 18);
console.log(person1.__proto__ === Person.prototype); // true

注意:__proto__ 并非仅限于 new 创建的对象——所有 JavaScript 对象都有 __proto__ ,它是实现原型链和属性查找机制的核心。


25f9dc512187487dae974cca3524977b.png

2. 原型链:继承与查找机制

2.1 原型链的结构

每个原型对象本身也是一个普通对象,因此它也有自己的 __proto__,从而形成一条链式结构,即原型链

例如:

console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null);             // true

这意味着:

  • Person.prototype 继承自 Object.prototype
  • Object.prototype 是整个原型链的终点,它的 __proto__null

2.2 原型对象的 constructor 属性

原型对象上有一个特殊的属性 constructor,默认指回其关联的构造函数:

function Person() {}
console.log(Person.prototype.constructor === Person); // true

const p = new Person();
console.log(p.constructor === Person); // true(通过原型链继承)

⚠️ 注意:如果你重写了整个 prototype 对象,会丢失默认的 constructor

function Person() {}
Person.prototype = {
    sayHi() { console.log('Hi'); }
};

// 此时 constructor 指向 Object!
console.log(new Person().constructor === Object); // true ❌

// 需要手动修复:
Person.prototype.constructor = Person;
console.log(new Person().constructor === Person); // true ✅

总结constructor 用于从实例反查其构造函数,但在重写 prototype 后需手动维护。

07426ae860144bbfab4f8b3fa3107e52.png

2.3 Object.prototype 的特殊地位

Object.prototype 是 JavaScript 原型体系的“根节点”,具有以下特性:

  • 所有对象(包括函数、数组、字面量等)最终都继承自它;
  • 它提供了通用方法,如 toString()valueOf()hasOwnProperty() 等;
  • __proto__null,标志着原型链的终点。
// 1. 字面量对象
const obj = {};
console.log(obj.__proto__ === Object.prototype); // true
console.log(obj.toString()); // "[object Object]"

// 2. 构造函数实例
function Person() {}
const p = new Person();
console.log(p.__proto__.__proto__ === Object.prototype); // true
console.log(p.hasOwnProperty); // function

// 3. 终点验证
console.log(Object.prototype.__proto__ === null); // true

因此,Object.prototype 是整个 JavaScript 对象世界的“根原型”。

ba39aeaa68b940e1aefe1427856f1650.png

2.4 原型链查找机制详解

来看一个典型例子:

var obj = new Object(); // {}
obj.species = '动物';

function Animal() {}
Animal.prototype = obj;

function Person() {}
Person.prototype = new Animal();

var su = new Person();
console.log(su.species, su.__proto__, su.toString());

原型链结构图解:

su (实例)
│
└── __proto__ → Person.prototype (即 new Animal())
    │
    └── __proto__ → Animal.prototype (即 obj)
        │
        ├── species: '动物'
        │
        └── __proto__ → Object.prototype
            │
            ├── toString: function
            │
            └── __proto__ → null

查找过程:

  • 访问 su.species

    1. su 自身无 species
    2. su.__proto__Person.prototype)→ 无;
    3. Person.prototype.__proto__(即 obj)→ 找到 '动物'
  • 调用 su.toString()

    1. sutoString
    2. Person.prototype 无;
    3. obj 无;
    4. Object.prototype 有 → 执行 "[object Object]"

关键规则

  • 沿 __proto__ 链向上查找;
  • 找到即停;
  • null 仍未找到,则返回 undefined

补充说明:

  • __proto__ 是非标准属性(已废弃),推荐使用 Object.getPrototypeOf(obj)
  • 若实例自身定义了与原型同名的属性,会**遮蔽(shadow)**原型上的属性。

3. 实践示例:创建小米 SU7 车型

3.1 定义构造函数

function Car(color) {
    this.color = color;
}

3.2 在 prototype 上定义共享属性和方法

Car.prototype = {
    drive() {
        console.log('drive, 下赛道');
    },
    name: 'su7',
    height: 1.4,
    weight: 1.5,
    long: 4800,
};
// 修复 constructor
Car.prototype.constructor = Car;

3.3 创建实例

const car1 = new Car('霞光紫');
console.log(car1.name, car1.weight); // su7, 1.5
car1.drive(); // "drive, 下赛道"

const car2 = new Car('海湾蓝');
console.log(car2.weight); // 1.5

所有 Car 实例共享同一套方法和默认属性,高效且节省内存。


4. 原型链的实际应用

4.1 实现继承

function Animal() {}
Animal.prototype.species = '动物';

function Person() {}
Person.prototype = new Animal(); // 继承 Animal
Person.prototype.sayHi = function() {
    console.log(`你好,我是${this.name}`);
};

const su = new Person();
su.name = 'shu老板';
console.log(su.species); // "动物"(继承自 Animal.prototype)
su.sayHi();              // "你好,我是shu老板"

此时 su 的原型链为:
su → Person.prototype → Animal.prototype → Object.prototype → null

附手绘图一张(字丑):

edf62c5013618340f6b2ef868df91ebf.jpg

4.2 动态修改原型的影响

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.species = '人类';

const p1 = new Person('张三', 18);
console.log(p1.species); // "人类"

// 修改原型
Person.prototype.species = '高等灵长类';
console.log(p1.species); // "高等灵长类" —— 实例自动“感知”变化

注意:修改原型会影响所有已有实例;但在实例上赋值不会影响原型


5. 总结

概念说明
构造函数用于初始化新对象,首字母通常大写
prototype函数的属性,指向原型对象,供实例共享
__proto__对象的属性,指向其原型,构成原型链
原型链通过 __proto__ 连接的链式结构,用于属性查找和继承
Object.prototype原型链的终点,提供基础方法
constructor原型对象上的属性,指回构造函数,重写 prototype 时需手动修复

理解 JavaScript 的原型与原型链,是掌握其面向对象编程的关键。它不仅解释了“继承如何工作”,也揭示了“为何所有对象都能调用 toString()”等底层机制。

掌握这些知识,你就能写出更高效、更符合 JavaScript 本质的代码。


💡 提示:现代开发中虽多用 class 语法,但其底层仍是基于原型链实现的。理解原型,才能真正驾驭 JavaScript。