一个有趣的原型继承实验:为什么“男人也会生孩子”?从对象赋值到构造函数继承的完整推演

在学习 JavaScript 面向对象时,很多人都会经历一个非常"离谱"的瞬间:

你给女人对象加了一个"生孩子"的方法

结果男人对象也能生孩子了

这不是业务 bug,这是原型和对象引用机制在给你上课。

这篇文章就带你完整复盘一个真实学习过程:

从最直觉的对象赋值继承

到出现诡异共享问题

再到构造函数 + 原型继承的正确解法

我们不仅看结果,更要看底层机制。


一、问题的起点:我只是想让两个对象拥有相同属性

假设我们有一个"人类基础属性":

  • 两只眼睛
  • 一个脑袋

最直觉的写法:

ini 复制代码
const Person = {
  eyes: 2,
  head: 1
}

然后想让:

  • Woman 拥有这些属性
  • Man 也拥有这些属性

很多人第一反应是:

ini 复制代码
const Woman = Person
const Man = Person

或者:

ini 复制代码
Woman.__proto__ = Person
Man.__proto__ = Person

看起来很合理。

毕竟目标只是"复用"。

但问题马上出现。


二、诡异现象:给女人加方法,男人也会了

现在给 Woman 添加一个方法:

javascript 复制代码
Woman.baby = function () {
  console.log('宝贝')
}

然后测试:

scss 复制代码
Man.baby()

居然也能调用。

为什么?

男人也会生孩子了。

这明显不合理。

但从 JavaScript 的角度,这是完全合理的。


三、本质原因:你根本没有创建多个对象

关键理解一句话:

复制代码
对象赋值不是复制,而是引用传递

当你写:

ini 复制代码
const Woman = Person

本质是:

复制代码
Woman 和 Person 指向同一个内存对象

内存结构:

markdown 复制代码
Person  ─┐
         ├── 同一个对象
Woman  ──┘
Man    ───┘

你不是创建三个对象。

你只是创建三个"变量名"。

它们共同指向一个对象。

所以:

给 Woman 添加属性

就是给 Person 添加属性

自然 Man 也能访问

问题不是继承错了。

是你压根没继承。

你只是多起了几个别名。


四、真正的目标:结构相同,但对象独立

我们希望:

  • Woman 有 eyes / head
  • Man 有 eyes / head
  • 但互不影响
  • 方法也可独立扩展

这意味着:

复制代码
必须创建多个独立对象

这就是构造函数存在的意义。


五、第一层解决方案:使用构造函数创建实例

构造函数的本质:

复制代码
批量生产结构相同的对象

实现:

csharp 复制代码
function Person() {
  this.eyes = 2
  this.head = 1
}

每次执行:

scss 复制代码
new Person()

都会发生:

1 创建新对象

2 绑定 this

3 执行函数

4 返回对象

关键点:

arduino 复制代码
每次 new 都创建全新对象

互不影响。


六、现在我们来做真正的"继承"

目标:

Woman 继承 Person

Man 继承 Person

传统原型继承写法:

javascript 复制代码
function Woman() {}
Woman.prototype = new Person()
Woman.prototype.constructor = Woman

同理:

javascript 复制代码
function Man() {}
Man.prototype = new Person()
Man.prototype.constructor = Man

这行代码非常关键:

ini 复制代码
Woman.prototype = new Person()

它做了什么?

不是复制代码。

是创建一个 Person 实例,然后把它作为 Woman 的原型。

结构变成:

javascript 复制代码
Woman实例
   ↓
Woman.prototype(Person实例)
   ↓
Object.prototype

Woman 的实例可以访问:

Person 实例里的属性。

这就是原型继承。


七、验证:现在给女人加能力,男人不会受影响

javascript 复制代码
Woman.prototype.baby = function () {
  console.log('宝贝')
}

测试:

scss 复制代码
new Woman().baby()   ✔
new Man().baby()     ✘

终于正常了。

原因:

Woman.prototype 和 Man.prototype 是两个不同对象。

虽然都来自 new Person()

但:

复制代码
是不同实例

互不干扰。


八、这就是关键转折:对象复用 vs 构造函数实例化

错误方案:

复制代码
共享同一个对象

正确方案:

复制代码
基于同一构造规则创建多个对象

这就是:

面向对象思想中的"实例化"。


九、构造函数为什么体现封装?

来看一个典型封装例子:

javascript 复制代码
function Person() {
  this.name = '佚名'

  this.setName = function (name) {
    this.name = name
  }

  this.getName = function () {
    console.log(this.name)
  }
}

创建两个实例:

csharp 复制代码
let p1 = new Person()
let p2 = new Person()

修改:

arduino 复制代码
p1.setName('小明')

结果:

ini 复制代码
p1.name = 小明
p2.name = 佚名

互不影响。

这就是封装:

复制代码
数据 + 操作数据的方法
打包在对象内部

并且实例独立。


十、但构造函数也有性能问题

如果方法写在构造函数里:

每个实例都会创建一份函数。

这很浪费内存。

解决方案:

使用原型。


十一、原型的真正作用:共享方法

javascript 复制代码
function Person() {}

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

所有实例共享:

复制代码
一份函数

而不是每人一份。

这就是原型的核心价值。


十二、实例是如何找到原型方法的?

访问对象属性时:

查找顺序:

1 先找实例自身

2 再找 prototype

3 再找上层原型

4 直到 null

这叫:

复制代码
原型链查找

相关推荐
还是大剑师兰特14 分钟前
Stats.js 插件详解及示例(完全攻略)
前端·大剑师·stats
前端小超超15 分钟前
Vue计算属性computed:可写与只读的区别
前端·javascript·vue.js
IT_陈寒1 小时前
SpringBoot实战:3个隐藏技巧让你的应用性能飙升50%
前端·人工智能·后端
weixin199701080161 小时前
唯品会商品详情页前端性能优化实战
前端·性能优化
爱学习的程序媛1 小时前
【Web前端】Pinia状态管理详解
前端·vue.js·typescript
爱学习的程序媛1 小时前
“数字孪生”详解与前端技术栈
前端·人工智能·计算机视觉·智慧城市·信息与通信
海石1 小时前
微信小程序开发02:原始人也能看懂的着色器与视频处理
前端·微信小程序·视频编码
程序员Sunday1 小时前
Claude Code 生态爆发:5个必知的新工具
前端·人工智能·后端
ChoSeitaku2 小时前
NO.2|proto3语法|消息类型|通讯录|文件读取|enum类型
java·服务器·前端
小J听不清2 小时前
CSS 边框(border)全解析:样式 / 宽度 / 颜色 / 方向取值
前端·javascript·css·html·css3