JavaScript 面向对象探秘:从构造函数到原型链的优雅继承

引言:万物皆对象的困惑

在 JavaScript 的世界里,我们习惯了"万物皆对象"。但与 Java、C++ 等传统面向对象语言不同,JavaScript 并没有类(ES6 之前的语法糖之下)的概念,而是基于原型(Prototype)构建的。

初学者往往对 thisprototype__proto__ 感到困惑:为什么方法要写在 prototype 上?实例是如何访问到构造函数之外的方法的?今天,我们就通过几个简单的代码片段,带你彻底搞懂 JS 的原型式面向对象。


1. 构造函数:对象的"工厂"

在 ES5 时代,我们使用首字母大写的函数作为构造函数来创建对象。构造函数解决了我们需要批量生产相似对象的问题。

看下面这段代码:

javascript 复制代码
function Car(color) {
  // this 指向新创建的实例
  this.color = color; 
}
// 共享属性
Car.prototype = {
  drive() { console.log('drive, 下赛道'); },
  name: 'su7'
}
const car1 = new Car('霞光紫');
car1.drive(); // "drive, 下赛道"

核心点: 构造函数内部的 this 指向新创建的实例,用于定义每个实例独有 的属性(如 color)。


2. 原型(Prototype):共享的"基因库"

如果把所有方法都写在构造函数里,每次 new 一个对象,内存中就会多一份方法的副本,这非常浪费资源。

JavaScript 的解决方案是 prototype。正如文档 8.md 所述:

"prototype 属性的值是一个对象,它上面的属性和方法会被所有实例共享。"

我们来看一个经典的 Person 案例:

ini 复制代码
function Person(name, age) { 
  this.name = name; 
  this.age = age; 
}
// 将属性挂载到原型上
Person.prototype.speci = '人类'; 

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

关键机制: 实例对象内部有一个私有属性 __proto__(现在标准推荐使用 Object.getPrototypeOf()),它指向构造函数的 prototype 对象。当访问 person1.speci 时,如果实例上没有,引擎就会去 Person.prototype 上找。


3. 原型链继承:模拟"血缘关系"

传统的 Class 面向对象是"血缘关系",而 JS 是"原型式"的。如何实现继承?答案是原型链

我们可以利用 prototype 指向另一个构造函数的实例,来实现属性的层层继承。:

javascript 复制代码
var obj = { species: '动物' };
function Animal() { }
Animal.prototype = obj; // Animal 继承了 obj 的属性

function Person() { }
Person.prototype = new Animal(); // Person 继承了 Animal

var su = new Person();
console.log(su.species); // "动物"

继承逻辑:

  1. su__proto__ 指向 Person.prototype(即 new Animal())。
  2. new Animal()__proto__ 指向 Animal.prototype(即 obj)。
  3. 当查找 su.species 时,引擎会沿着这条链一直找到 obj 上的 species

4. 原型链的终点:Object.prototype

所有的对象,最终都会指向 Object.prototype。这也是为什么我们所有的对象都能调用 .toString() 方法的原因。

arduino 复制代码
// 6.html
console.log(su.toString()); // 能调用,因为原型链最终指向了 Object.prototype
console.log(su.__proto__.__proto__); // 指向 Object.prototype

注意: Object.prototype__proto__ 指向 null,标志着原型链的结束。


5. 雷点和实践

在使用原型时,有一个容易踩的坑:

ini 复制代码
// 5.html
Person.prototype.species = '人类';
var su = new Person();
su.species = 'LOL达人'; // 这是在实例上新建了一个属性,而不是修改原型

解释: 当你给实例设置一个与原型同名的属性时,JS 引擎会在实例上直接创建该属性(遮蔽效应),而不会修改原型上的值。如果你删除了实例的这个属性,它依然会回到原型上取值。


结语

理解构造函数、实例与原型三者的关系,是掌握 JavaScript 面向对象的基石。

  • 构造函数是模版(Constructor)。
  • 实例是具体的对象。
  • 原型是所有实例共享的属性和方法的容器。
  • 原型链是实现继承的机制。

最后用一张图总结下 助你更好理解原型和构造函数

图的上半部分主要展示了自定义构造函数 Person 的内部关系:

  • 构造函数 Person

    • 它是一个函数,用来通过 new 关键字创建实例(如 new Person())。
    • 它有一个指向原型对象的属性:prototype
  • 原型对象 Person.prototype

    • 这是构造函数的"原型",它是一个对象。
    • 它有一个指向构造函数的属性:constructor
    • 关系Person.prototype.constructor 指向 Person。这是一个循环引用,确保原型知道是谁构造了它。
  • 实例对象 person

    • 这是通过 new Person() 创建出来的具体对象。
    • 它有一个内部指针: __proto__ (注意:这是非标准但广泛支持的属性,标准中对应 [[Prototype]])。
    • 关系person.__proto__ 指向 Person.prototype。这是原型链的核心:实例通过这个指针去原型对象上查找方法和属性。

小结 :实例的 __proto__ 指向构造函数的 prototype,而 prototypeconstructor 又指回构造函数。

2. 继承的终点(下半部分)

图的下半部分展示了所有对象的最终归宿------Object

  • Object()

    • 这是 JS 内置的顶级构造函数。
    • 同样,Object.prototypeconstructor 指向 Object
  • 连接点

    • 注意看中间那条向下的箭头:Person.prototype__proto__ 指向了 Object.prototype
    • 这意味着Person 的原型对象本身也是一个对象(它是由 Object 构造出来的),所以它也要遵循对象的规则,去继承 Object.prototype 上的通用方法(如 toStringhasOwnProperty 等)。

相关推荐
蜗牛前端13 分钟前
codex 全流程开发上线的高颜值礼簿小程序
前端·微信小程序
大龄秃头程序员1 小时前
我在图文流 App 里落地双层缓存、弱网降级与 OOM 治理
前端
老王以为1 小时前
React Renderer 分离的多平台架构
前端·react native·react.js
hunterandroid1 小时前
Kotlin Coroutines 与 Flow:让异步任务更清晰
前端
Bigger2 小时前
从零搭建 AI 代码审查服务:一份前端也能看懂的 Python 学习笔记
前端·ci/cd·ai编程
lichenyang4532 小时前
JSAPI、NAPI、Biz、Imp:ASCF Demo 如何真正调用系统能力和 C++ 能力
前端
lichenyang4532 小时前
IPC、JSVM、UIThread、libuv:ASCF 架构图里最容易混的几个词
前端
用户059540174462 小时前
Redis记忆存储故障恢复测试踩坑实录:手动测试让我漏掉了2个一致性Bug
前端·css
用户2136610035722 小时前
Vue2脚手架工程化与Axios集成
前端·vue.js
张元清2 小时前
React useDebounce Hook:给状态和回调做防抖(2026)
javascript·react.js