JavaScript 原型与原型链:从零到精通的深度解析

"JavaScript 的一切皆对象,而对象的背后,是原型。"

------ 本文将带你彻底、系统、深入地理解 JavaScript 中最核心、最独特、也最容易被误解的机制:原型(Prototype)原型链(Prototype Chain)。结合代码,逐行注解、层层递进,确保你不仅能"知道",更能"掌握"和"运用"。

引言:为什么原型如此重要?

在前端开发中,无论是面试大厂,还是阅读框架源码(如 React、Vue),亦或是编写高性能、可复用的组件,原型机制都是绕不开的核心知识。

很多开发者能写出 classextends,却说不清 prototype__proto__ 的区别;能调用 toString(),却不明白它到底从哪来。这种"黑盒式"编程,在遇到复杂问题时极易陷入困境。

本文的目标,就是撕开 JavaScript 面向对象的面纱,让你看清其底层运作逻辑。我们将从最基础的概念出发,通过真实代码、图示、类比和深度剖析,构建完整的认知体系。


第一章:JavaScript 的面向对象哲学 ------ 原型式 vs 类式

1.1 传统"类式"面向对象(Class-based OOP)

以 Java、C++、Python 为例:

  • 先定义一个 类(Class),作为模板。
  • 类包含属性和方法。
  • 通过 new Class() 创建 实例(Instance)
  • 实例"继承"类的结构,但拥有自己的属性值。
  • 类与实例之间存在"血缘关系"------实例是类的"后代"。
java 复制代码
// Java 示例
class Car {
  String color;
  Car(String c) { this.color = c; }
  void drive() { System.out.println("driving"); }
}

Car myCar = new Car("red");

这里,myCarCar 类的实例,二者有明确的类型归属。

1.2 JavaScript 的"原型式"面向对象(Prototypal OOP)

JavaScript 没有真正的类 (ES6 的 class 只是语法糖)。它的核心思想是:

对象直接从其他对象继承行为,而不是从抽象的"类"模板。

这被称为 "基于原型的继承"(Prototypal Inheritance)

  • 没有"类",只有对象
  • 一个对象可以作为另一个对象的原型(Prototype)
  • 当访问一个对象的属性时,如果自身没有,就去它的原型找;原型没有,再去原型的原型找......直到找到或到达终点。

这种机制更灵活、动态,但也更抽象。

"JS的面向对象是'原型式'而非'类式',实例与原型间无血缘关系,而是基于共享与委托的机制。"


第二章:构造函数 ------ 创造实例的"工厂"

要理解原型,必须先理解构造函数(Constructor Function)

2.1 什么是构造函数?

在 JavaScript 中,任何函数都可以作为构造函数使用 ,只要用 new 关键字调用。

javascript 复制代码
// 定义一个普通函数 Person
// 但它将被用作构造函数
function Person(name, age) { 
  // 当使用 new Person() 时,
  // JavaScript 引擎会创建一个新空对象 {}
  // 并将 this 绑定到这个新对象上
  this.name = name;   // 给新对象添加 name 属性
  this.age = age;     // 给新对象添加 age 属性
  // 函数执行完毕后,自动 return this(除非显式返回对象)
}

2.2 使用 new 创建实例

javascript 复制代码
const person1 = new Person('张三', 18);
const person2 = new Person('李四', 19);
  • person1person2 是两个独立的对象
  • 它们各自拥有 nameage 属性,互不影响。
  • 这些属性称为实例属性(Instance Properties)

✅ 关键点:

构造函数内部的 this 指向新创建的实例对象

实例属性是每个对象私有的。


第三章:原型(Prototype)------ 共享行为的"公共仓库"

如果每个实例都要有自己的方法(如 sayHello),那会浪费大量内存。于是,JavaScript 引入了 prototype

3.1 每个函数都有 prototype 属性

这是 JavaScript 引擎自动为函数添加的属性。

javascript 复制代码
function Car(color) {
  this.color = color;
}
console.log(Car.prototype); // { constructor: Car }
  • Car.prototype 是一个对象
  • 默认情况下,它只有一个属性:constructor,指向 Car 本身。
  • 这个对象就是未来所有 Car 实例的原型

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

继续看:

javascript 复制代码
// es5 没有类class
// JS 函数是一等对象 
function Car(color){
    // this 指向新创建的对象
    this.color = color;
    // this.drive
    // 车参数
    // this.name = '小米su7';
    // this.price = 300000;
}

Car.prototype = {
    drive(){
        console.log('drive');
    },
    name: '小米su7',
    price: 300000,
}
逐行深度解析:
  1. function Car(color){ ... }

    • 定义构造函数,接收 color 参数。
    • 每个实例将拥有自己的 color
  2. Car.prototype = { ... }

    • 重写整个原型对象
    • 原来的 { constructor: Car } 被替换了。
    • 新原型包含:
      • drive() 方法
      • name 属性(值为 '小米su7'
      • price 属性(值为 300000
  3. 创建实例并使用:

javascript 复制代码
const car1 = new Car('霞光紫');
console.log(car1, car1.name, car1.price); // 输出实例 + 共享属性
car1.drive(); // 调用原型上的方法

const car2 = new Car('海湾蓝');
console.log(car2, car2.name, car2.price);
  • car1.color'霞光紫'(实例属性)
  • car1.name'小米su7'(来自 Car.prototype
  • car1.drive() → 调用 Car.prototype.drive

✅ 核心优势:
namepricedrive 只在内存中存储一份,所有实例共享,极大节省内存!


第四章:__proto__ ------ 实例通往原型的"秘密通道"

现在问题来了:实例是如何访问到原型上的属性的?

答案是:通过 __proto__

4.1 什么是 __proto__

  • 每个对象 (包括数组、函数、普通对象)都有一个内部属性 [[Prototype]]
  • 在浏览器中,可通过非标准但广泛支持的 __proto__ 访问它。
  • obj.__proto__ 指向该对象的原型对象

4.2 实例的 __proto__ 指向构造函数的 prototype

例如:

javascript 复制代码
const person1 = new Person('张三', 18);
console.log(person1.__proto__, '////');

输出结果将是:

javascript 复制代码
{ speci: '人类' } '////'

因为:

javascript 复制代码
Person.prototype.speci = '人类'; // 在原型上添加属性

所以:

javascript 复制代码
person1.__proto__ === Person.prototype; // true

✅ 这是 JavaScript 原型机制的基石关系

4.3 图解:实例、构造函数、原型的关系

复制代码
          +------------------+
          |   Person (函数)  |
          +------------------+
          | prototype        |----+
          +------------------+    |
                                  ↓
+------------------+     +------------------+
|   person1 (对象) |     | Person.prototype |
+------------------+     +------------------+
| name: '张三'     |     | speci: '人类'    |
| age: 18          |     | constructor: Person
+------------------+     +------------------+
| __proto__        |-----^
+------------------+
  • person1 自身有 nameage
  • 当访问 person1.speci 时:
    • 先查 person1 自身 → 没有。
    • 再查 person1.__proto__(即 Person.prototype)→ 找到 '人类'

第五章:原型链(Prototype Chain)------ 属性查找的"寻宝地图"

原型不仅可以一层,还可以多层嵌套,形成链式结构 ,这就是原型链

5.1 原型链的形成过程

  1. 实例对象 → __proto__ → 构造函数的 prototype
  2. 构造函数的 prototype 本身也是对象 → 它也有 __proto__
  3. 默认情况下,Function.prototypeObject.prototype 成为上一级
  4. 最终,所有链都指向 Object.prototype
  5. Object.prototype.__proto__ === null链终止

5.2 完整原型链示例

car1 为例:

javascript 复制代码
const car1 = new Car('霞光紫');

其原型链如下:

复制代码
car1
  │
  └─ __proto__ → Car.prototype
                    │
                    ├─ drive()
                    ├─ name: '小米su7'
                    ├─ price: 300000
                    │
                    └─ __proto__ → Object.prototype
                                      │
                                      ├─ toString()
                                      ├─ hasOwnProperty()
                                      ├─ isPrototypeOf()
                                      │
                                      └─ __proto__ → null

5.3 属性查找规则(覆盖机制)

当你访问 obj.prop 时,JavaScript 引擎执行以下步骤:

  1. 检查 obj 自身是否有 prop → 有则返回。
  2. 没有?检查 obj.__proto__(即原型)是否有 prop → 有则返回。
  3. 还没有?继续沿 __proto__ 向上查找
  4. 直到 Object.prototype
  5. 若仍未找到,返回 undefined

"实例上有就用自己的,没有就去原型上找。"

5.4 覆盖 vs 修改

javascript 复制代码
person1.speci = '外星人';

console.log(person1.speci);       // '外星人'(实例属性)
console.log(person2.speci);       // '人类'(原型属性)
console.log(Person.prototype.speci); // '人类'
  • person1.speci = '外星人' 没有修改原型
  • 而是在 person1新建了一个同名属性覆盖了原型上的值。
  • 这体现了"实例优先"原则。

✅ 修改共享属性的正确方式:

javascript 复制代码
Person.prototype.speci = '智人';
// 所有未覆盖的实例都会看到新值

第六章:Object.getPrototypeOf() ------ 安全获取原型的标准方法

虽然 __proto__ 可用,但它是非标准属性,且可能被滥用。

ES5 引入了标准 API:

javascript 复制代码
Object.getPrototypeOf(obj)

它等价于 obj.__proto__,但更安全、规范。

javascript 复制代码
console.log(person1.__proto__, '////');
// 或写成:
console.log(Object.getPrototypeOf(person1), '////');

推荐使用 Object.getPrototypeOf(obj) 来安全获取对象的原型,优于直接访问 proto。大厂面试常考察此 API。


第七章:万物皆对象?连函数也是!

JavaScript 中,函数也是对象

javascript 复制代码
function fn() {}
console.log(fn instanceof Object); // true

因此,函数也有 __proto__

javascript 复制代码
function Car() {}
console.log(Car.__proto__ === Function.prototype); // true

Function.prototype 本身也是函数,它的 __proto__ 指向 Object.prototype

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

这解释了为什么函数也能调用 toString()hasOwnProperty() 等方法。


第八章:对象字面量也有原型!

即使不用构造函数,直接创建对象:

javascript 复制代码
const obj = {};

它依然有原型:

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

对象字面量也具有 proto 属性,说明即使非构造函数创建的对象依然遵循原型机制。

所以,{}[]/regex/ 等字面量,都自动链接到对应的内置原型。


第九章:constructor 属性 ------ 原型的"回指针"

每个原型对象都有一个 constructor 属性,指向其构造函数。

javascript 复制代码
Person.prototype.constructor === Person; // true
Car.prototype.constructor === Car;       // 在 1.js 中为 false!

为什么 Car 不成立?

因为在 1.js 中,我们重写了整个 prototype 对象

javascript 复制代码
Car.prototype = {
  drive() { ... },
  name: '小米su7',
  price: 300000,
};
// 此时 Car.prototype.constructor === Object!

修复方法:

javascript 复制代码
Car.prototype = {
  constructor: Car, // 手动指定
  drive() { ... },
  name: '小米su7',
  price: 300000,
};

或者使用 Object.defineProperty 设置不可枚举:

javascript 复制代码
Object.defineProperty(Car.prototype, 'constructor', {
  value: Car,
  writable: true,
  configurable: true,
  enumerable: false // 默认不遍历
});

第十章:原型链的终点 ------ Object.prototypenull

所有原型链最终都指向 Object.prototype,而它的 __proto__null

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

null 表示"没有原型",是查找的终止符

null表示没有原型对象,null在此处的作用是终止查找流程,防止内存溢出问题。

如果没有 null,引擎会无限循环查找,导致崩溃。


第十一章:高级应用 ------ 实现继承

利用原型链,我们可以实现"类继承"。

11.1 ES5 继承(组合寄生式)

javascript 复制代码
function Vehicle(type) {
  this.type = type;
}
Vehicle.prototype.move = function() {
  console.log('moving');
};

function Car(color) {
  Vehicle.call(this, 'car'); // 借用构造函数
  this.color = color;
}

// 设置原型链
Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.constructor = Car;

Car.prototype.drive = function() {
  console.log('driving');
};

const myCar = new Car('red');
myCar.move(); // 'moving'(来自 Vehicle.prototype)
myCar.drive(); // 'driving'

这里,Car.prototype.__proto__ === Vehicle.prototype,形成两层原型链。

11.2 ES6 class 的本质

javascript 复制代码
class Parent {
  say() { console.log('parent'); }
}

class Child extends Parent {
  speak() { console.log('child'); }
}

等价于:

javascript 复制代码
function Parent() {}
Parent.prototype.say = function() { ... };

function Child() {
  Parent.call(this);
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.speak = function() { ... };

所以,class 只是语法糖,底层仍是原型链!


第十二章:常见误区与面试题

❌ 误区1:prototype 是所有对象都有的属性

错! 只有函数 才有 prototype

普通对象有 __proto__,但没有 prototype

复制代码
const obj = {};
console.log(obj.prototype); // undefined
console.log(obj.__proto__); // Object.prototype

❌ 误区2:修改实例属性会影响原型

错! 实例属性覆盖原型属性,但不会修改原型。

✅ 面试题:如何判断一个属性是实例自有还是继承的?

javascript 复制代码
obj.hasOwnProperty('prop'); // true 表示自有属性

✅ 面试题:instanceof 的原理?

javascript 复制代码
obj instanceof Constructor
// 等价于:检查 Constructor.prototype 是否在 obj 的原型链上

第十三章:总结 ------ 原型机制全景图

概念 说明 所属
prototype 函数的属性,指向原型对象 函数
__proto__ 对象的属性,指向其原型 所有对象
constructor 原型对象的属性,指回构造函数 原型对象
原型链 obj → __proto__ → prototype → __proto__ → ... → Object.prototype → null 查找路径
共享机制 方法和公共属性放在 prototype 上,节省内存 设计哲学
覆盖机制 实例属性优先于原型属性 查找规则

原型和原型链

  • prototype是函数的一个属性,指向一个对象
    示例:

    javascript 复制代码
    function Person() {
      this.name = '张三';
    }
    console.log(Person.prototype);
     // 表示Person这个函数的原型对象prototype是{name: '张三'}
  • __proto__是实例对象的一个属性,指向它的原型对象
    示例:

    javascript 复制代码
    const person = new Person();
    console.log(person.__proto__);
     // 即person这个实例对象的__proto__是{name: '张三'}
  • constructor是实例对象的一个属性,指向它的构造函数
    示例:

    javascript 复制代码
    console.log(person.__proto__.constructor);
     // 即{name: '张三'}.constructor 是 Person这个函数

结语:掌握原型,掌握 JavaScript 的灵魂

JavaScript 的原型机制,是其区别于其他语言的最大特色。它看似复杂,实则逻辑严密、设计精巧。

通过本文,你已经:

  • 理解了构造函数与实例的关系;
  • 掌握了 prototype__proto__ 的区别与联系;
  • 学会了原型链的查找规则;
  • 能解释 1.js2.js 中每一行代码的含义;
  • 知道了如何安全操作原型;
  • 能应对相关面试题。

记住:

不是"类"创造了对象,而是"对象"链接了对象。

这就是 JavaScript 的原型之美。

愿你在编程之路上,越走越远,越走越深!🚀


附:关键代码汇总(保留原始格式,逐行注解)

1.js 完整注解

javascript 复制代码
// es5 没有类class
// JS 函数是一等对象 
function Car(color){
    // this 指向新创建的对象(当使用 new 调用时)
    this.color = color; // 实例属性,每个 car 有自己的颜色
    // this.drive        // 此时 this 上还没有 drive 方法
    // 车参数
    // this.name = '小米su7';  // 如果放这里,每个实例都会复制一份,浪费内存
    // this.price = 300000;
}

// 重写 Car 的原型对象,用于共享属性和方法
Car.prototype = {
    // drive 是一个方法,所有实例共享
    drive(){
        console.log('drive');
    },
    // 共享属性:所有 Car 实例的 name 都是 '小米su7'
    name: '小米su7',
    // 共享属性:价格
    price: 300000,
    // 注意:此处丢失了 constructor 属性!
}

// 创建两个实例
const car1 = new Car('霞光紫');
// car1 自身有 color: '霞光紫'
// car1.name 来自 Car.prototype
console.log(car1, car1.name, car1.price);

// 调用原型上的方法
car1.drive();

const car2 = new Car('海湾蓝');
console.log(car2, car2.name, car2.price);

2.js 完整注解

javascript 复制代码
// 定义 Person 构造函数
function Person(name, age) {
  // 实例属性
  this.name = name; 
  this.age = age;
}

// 在 Person.prototype 上添加共享属性 speci(应为 species,但保留原拼写)
Person.prototype.speci = '人类';

// 创建实例
const person1 = new Person('张三', 18);
// person1 自身有 name、age;speci 来自原型
console.log(person1.name, person1.speci); // 张三 人类

const person2 = new Person('李四', 19);
console.log(person2.name, person2.speci); // 李四 人类

// 打印 person1 的原型(即 Person.prototype)
console.log(person1.__proto__, '////');
相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅6 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax