原型与原型链:从 proto 到 class 继承,一次讲透对象关系的本质

举报
yd_252100366 发表于 2026/10/16 10:42:18 2026/10/16
【摘要】 ## 一、先记住三者的关系每个函数都有 `prototype`,每个对象都有 `__proto__`,对象默认还有个 `constructor` 指回构造函数:```jsfunction Person(name) {  this.name = name;}const tom = new Person('Tom');```三者的指向规则:- `Person.prototype` → 原型对象(...

## 一、先记住三者的关系

每个函数都有 `prototype`,每个对象都有 `__proto__`,对象默认还有个 `constructor` 指回构造函数:

```js
function Person(name) {
  this.name = name;
}
const tom = new Person('Tom');
```

三者的指向规则:

- `Person.prototype` → 原型对象(`{ constructor: Person }`)
- `tom.__proto__` === `Person.prototype`(实例的隐式原型指向构造函数的显式原型)
- `Person.prototype.constructor` === `Person`
- 原型对象本身也是普通对象,它的 `__proto__` 指向 `Object.prototype`
- `Object.prototype.__proto__` === `null`,链条到此终止

判定原型用 `Object.getPrototypeOf(obj)`,`__proto__` 只是它的访问器形式,二者等价。

## 二、属性查找沿原型链向上搜索

读一个属性时,引擎先在**对象自身**找,找不到就顺着 `__proto__` 一层层往上,直到 `null`:

```js
tom.toString(); // tom 自身没有 → Person.prototype 没有 → Object.prototype 有 → 用之
```

这条链就是"原型链"。两个直接推论:

1. **原型上的引用类型属性会被所有实例共享**——在 `Person.prototype.list = []` 上 push,所有实例的 list 都变。数组、对象这类值必须放构造函数里用 `this` 创建。
2. **写入不会触发原型链**:给 `tom.name = 'x'` 是给自身加属性,读才查链。想让修改生效到原型上,必须显式 `tom.__proto__.name = 'x'`。

## 三、继承的四种写法演进

### 1. 原型链继承(有缺陷)

```js
function Child() {}
Child.prototype = new Parent(); // 实例共享父类引用属性,且传不了参
```

### 2. 构造函数借用(call)

```js
function Child(name) {
  Parent.call(this, name); // 拿到实例属性,但拿不到原型方法
}
```

### 3. 组合继承(经典)

```js
function Child(name) {
  Parent.call(this, name);
}
Child.prototype = new Parent(); // 重复调用一次,浪费但不影响正确性
```

### 4. 寄生组合继承(最优解)

```js
function Child(name) {
  Parent.call(this, name);
}
Child.prototype = Object.create(Parent.prototype); // 只借原型,不调用父构造函数
Child.prototype.constructor = Child;
```

`Object.create(proto)` 直接以指定对象为原型创建新对象,避免了组合继承那次多余的父类调用。**class extends 编译后就是这个模式**。

## 四、class 是语法糖,但有两个细节不同

```js
class Child extends Parent {
  constructor(name) {
    super(name); // 必须先调 super 才能用 this
  }
}
```

编译成 ES5 后核心就是寄生组合继承,但有两个行为差异要知道:

1. **class 的方法不可枚举**:挂在 prototype 上但 `enumerable: false`,而 `Person.prototype.say = fn` 是可枚举的,`for...in` 能遍历到。
2. **class 必须 new 调用**:直接 `Person('x')` 会抛 TypeError,ES5 函数不会。

## 五、两个高频误区

1. **instanceof 的判断逻辑**:`a instanceof B` 查的是 `B.prototype` 是否出现在 `a` 的原型链上,不是"看构造函数名"。所以 `[] instanceof Object` 是 `true`,跨 iframe 的对象判断还会失效。要精确判断数组用 `Array.isArray`。
2. **Object.create(null) 创建的对象没有原型**:`__proto__` 是 null,`toString` 都不存在,但它是纯字典对象,`for...in` 干净无继承属性,适合做映射表(Vue 源码里大量使用)。

## 六、手写 new(检验掌握程度)

```js
function myNew(constructor, ...args) {
  const obj = Object.create(constructor.prototype);
  const result = constructor.apply(obj, args);
  return result instanceof Object ? result : obj;
}
```

三步:以原型创建对象、绑定 this 执行、构造函数返回对象则用返回值,否则用新对象。理解了这三步,原型链的核心就通了。

## 七、总结

原型关系的记忆锚点就一句:**实例.__proto__ === 构造函数.prototype,链条终点是 Object.prototype.__proto__ === null**。读属性沿链向上,写属性落在自身;继承的最优解是寄生组合,class 只是它的语法糖。抓住这条主线,所有原型题都能自己推演。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。