JavaScript 原型链:一张图讲透「对象继承」的底层真相

很多同学背过这样一句话:"JavaScript 是通过原型链(Prototype Chain)实现继承的" 。但真被问到"原型链是怎么串起来的"时,往往只能答出"__proto__ 指来指去"。

这篇文章的目标只有一个:让你在脑中建立起一张永不忘记的结构图 ,之后所有关于原型、继承、instanceof 的问题,都从这张图出发推理,而不是死记结论。


1. 为什么需要原型链:JS 的对象模型

Java / C++ 的继承靠"类(Class)",是先有类,再实例化对象。补充一点: ES6 正式发布了 JS 的 class,但他不是"类模型",而是基于已有的(函数 + prototype + 原型链)写出来的语法糖,本质上还是原型链的那一套内容。

JS 不一样------JS 从设计之初(1995 年,Brendan Eich 用 10 天完成)就只有对象 。那对象之间怎么共享方法?答案:让一个对象"借用"另一个对象的能力

这个"借用"关系就是原型链:

  • 每个对象内部都藏着一个隐式引用 [[Prototype]](浏览器里可通过 __proto__ 访问);
  • 访问属性时,如果自己身上没有,就沿着 [[Prototype]] 一直向上找;
  • 这条"对象 → 原型 → 原型的原型 → ..."的链条,就叫原型链

一句话总结:原型链 = 对象间共享能力的机制 + 属性查找的规则。


2. 三个核心概念:prototype / proto / constructor

搞清楚这三个东西,原型链就懂了一半。请先背下这张对照表:

概念 谁拥有 指向哪里 作用
prototype 只有函数有(箭头函数除外) 指向一个普通对象 new 创建实例时,实例的原型指向它
__proto__ 每个对象都有(函数也是对象) 指向"创建我的那个构造函数的 prototype" 组成原型链的"链环"
constructor 每个函数的 prototype 对象上有 指回这个函数本身 让原型知道"我是谁构造的"

最容易混淆的点(先记住,后面还会展开):

js 复制代码
person.__proto__      // 实例 → 原型          (对象到对象)
Person.prototype      // 函数 → 原型          (函数到对象)
Person.prototype.constructor === Person       // 原型 → 函数(回指)

3. 原型链长什么样:结构总览

我们定义一个构造函数,然后 new 出实例,看看这三者的关系网:

js 复制代码
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.sayHi = function () {
  console.log(`Hi, I'm ${this.name}`);
};

const person1 = new Person('Tom', 18);

它背后的结构是下面这张图(请把这张图刻进脑子里):

读图要点:

  1. 实线箭头是 __proto__,它把"实例 → 原型 → 内置原型 → null"串成一条链;
  2. 虚线箭头是 prototypeconstructor 的互相指认,它们构成"三角关系",但不参与属性查找;
  3. 链条最终指向 null------原型链是有终点的,不是无限循环。

验证一下(这几个等式能答对,你就真的懂了):

js 复制代码
person1.__proto__ === Person.prototype;          // true  实例的原型是构造函数的 prototype
Person.prototype.constructor === Person;         // true  原型的 constructor 回指函数
Person.prototype.__proto__ === Object.prototype; // true  原型对象本身也是对象,它的原型是内置 Object.prototype
Object.prototype.__proto__ === null;             // true  链的终点

4. new 到底做了什么:逐行拆解

new Person('Tom', 18) 不是魔法,它本质上做了 4 步。手写一个等价实现:

js 复制代码
function myNew(Ctor, ...args) {
  // 第 1 步:创建一个全新的空对象
  const obj = {};

  // 第 2 步:把空对象的原型指向构造函数的 prototype ------ 关键!这一步决定了 obj 能"继承"什么
  obj.__proto__ = Ctor.prototype;

  // 第 3 步:以 obj 为 this 执行构造函数,把实例属性挂上去
  const result = Ctor.apply(obj, args);

  // 第 4 步:构造函数若显式返回了对象,则用那个对象;否则返回 obj
  return (result !== null && typeof result === 'object') ? result : obj;
}

const person2 = myNew(Person, 'Jerry', 20);
console.log(person2.sayHi()); // Hi, I'm Jerry ------ 方法来自原型链,属性来自构造函数

逐行解释:

步骤 代码 作用
1 const obj = {} 造一个裸对象,它此刻还"一无所有"
2 obj.__proto__ = Ctor.prototype 建立原型链的瞬间 :让 obj 能沿链找到 sayHi 等方法
3 Ctor.apply(obj, args) this 指向 obj,执行 this.name = ...,把属性装进 obj
4 判断返回值 模拟 new 的特殊规则:构造函数返回对象时,new 会丢弃自己造的对象

第 2 步就是"继承"的真相:所谓继承,本质是实例的原型指向了构造函数的 prototype


5. 属性查找:沿链向上,命中即停

这是原型链最核心的行为。规则只有一句话:访问 obj.prop 时,先从自身找,找不到就沿着 __proto__ 一层层向上找,直到 null

5.1 遮蔽(Shadowing):自身属性优先

js 复制代码
Person.prototype.name = '原型上的名字';
const p = new Person('实例名', 20);

console.log(p.name); // '实例名' ------ 自身属性优先,遮蔽了原型上的同名属性

delete p.name;       // 删掉自身的 name
console.log(p.name); // '原型上的名字' ------ 自身没了,才沿链找到原型

注意陷阱p.name = 'x' 只会给 p 自身新增 一个属性(遮蔽原型),永远不会去改原型上的 name 。要改原型,得显式 Person.prototype.name = 'y'

5.2 一个经典查找演示

js 复制代码
console.log(person1.sayHi());     // 自身没有 → 在 Person.prototype 找到 ✅
console.log(person1.toString());  // 自身、Person.prototype 都没有 → 在 Object.prototype 找到 ✅
console.log(person1.valueOf());   // 同上,Object.prototype 提供 ✅
console.log(person1.x);           // 整条链都没有 → undefined ✅

6. 链的尽头:Object.prototype 与 null

  • 所有普通对象的原型链终点都是 Object.prototype ,它提供了 toString / valueOf / hasOwnProperty 等"人人可用"的方法;
  • Object.prototype__proto__null,链到此为止,再往上没有了。
js 复制代码
const obj = {};
console.log(obj.__proto__ === Object.prototype); // true  ------ 所有字面量对象的原型都是它
console.log(Object.prototype.__proto__);         // null  ------ 终点
console.log({}.toString());                      // '[object Object]' ------ 从 Object.prototype 借来的

7. 常用工具方法:别只会用,要懂原理

7.1 hasOwnProperty ------ 只看自身

js 复制代码
const p = new Person('Tom', 18);
console.log(p.hasOwnProperty('name'));     // true  ------ name 是自身属性
console.log(p.hasOwnProperty('sayHi'));    // false ------ sayHi 在原型上,不是自身属性

7.2 in 操作符 ------ 含原型链

js 复制代码
console.log('name' in p);       // true  自身有
console.log('sayHi' in p);      // true  原型链上有也算
console.log('toString' in p);   // true  一路查到了 Object.prototype

区分口诀hasOwnProperty 只看"自己兜里有没有",in 看"整条链上有没有"。

7.3 instanceof ------ 沿原型链做"成员检查"

js 复制代码
console.log(person1 instanceof Person); // true
console.log(person1 instanceof Object); // true ------ Object.prototype 也在链上
console.log(person1 instanceof Array);  // false

原理obj instanceof Ctor 等价于------判断 Ctor.prototype 是否出现在 obj 的原型链上。手写:

js 复制代码
function myInstanceof(obj, Ctor) {
  let proto = obj.__proto__;
  while (proto !== null) {
    if (proto === Ctor.prototype) return true;
    proto = proto.__proto__;   // 沿链向上走
  }
  return false;
}

console.log(myInstanceof(person1, Person)); // true
console.log(myInstanceof(person1, Object)); // true

7.4 Object.create ------ 手动指定原型

不想用构造函数?Object.create(proto) 直接造一个"原型是你指定的对象"的新对象:

js 复制代码
const base = { greet() { return 'hello'; } };
const obj = Object.create(base);

console.log(obj.greet());            // 'hello' ------ 继承自 base
console.log(obj.__proto__ === base); // true ------ 原型被直接指定为 base
console.log(obj.hasOwnProperty('greet')); // false ------ greet 在原型上

注意Object.create(null) 可以造出"没有任何原型"的裸对象(常用于字典/缓存,不会被原型方法干扰)。


8. class 与 extends:语法糖下的原型链

class 是 ES6 引入的语法糖,底层依然是原型链。别被它"像 Java 的类"的外表骗了。

js 复制代码
class Person {
  constructor(name) { this.name = name; }   // 对应原来的构造函数
  sayHi() { return `Hi, ${this.name}`; }    // 自动挂到 Person.prototype 上
}

class Student extends Person {              // extends = 设置原型链继承
  constructor(name, grade) {
    super(name);                            // super() = 调用父类构造函数
    this.grade = grade;
  }
  study() { return `${this.name} 在写作业`; }
}

const s = new Student('小明', '高一');
console.log(s.sayHi());  // Hi, 小明 ------ 沿链找到 Person.prototype.sayHi
console.log(s.study());  // 小明 在写作业 ------ 找到 Student.prototype.study

它的原型链结构如下(注意有两条链):

两张"链"要分清:

链条 用途 关键等式
实例链s → Student.prototype → Person.prototype → Object.prototype → null 实例找方法 s.__proto__ === Student.prototype
类链Student.__proto__ === Person 类找静态方法 Student.extend === Person.extend(若父类有静态方法)

所以 class 让 JS 同时拥有了"实例继承"和"静态成员继承",而底层都是原型引用。


9. 常见误区避坑:一张表说清

# 误区说法 真相
1 "__proto__prototype 是一回事" __proto__每个对象 的隐式原型(链环);prototype函数独有的属性,用来给实例当原型
2 "普通对象也有 prototype" 只有函数有 prototype;普通对象(包括实例)只有 __proto__
3 "箭头函数可以用作构造函数" 箭头函数没有 prototype ,也不能被 new
4 "原型链是无限循环的" 终点是 nullObject.prototype.__proto__ === null
5 "obj.name = 'x' 会修改原型" 只会给 obj 自身 新增属性(遮蔽),不会动原型;改原型要显式操作 Ctor.prototype
6 "class 是全新的继承机制" 纯语法糖,底层仍是原型链;extends 就是在设置 Student.prototype.__proto__
7 "instanceof 检查实例的构造函数" 它检查的是 Ctor.prototype 是否在实例的原型链上,跟实例是谁 new 出来的没有直接关系

10. 面试高频题:会做才算真懂

Q1:手写 new

js 复制代码
function myNew(Ctor, ...args) {
  const obj = Object.create(Ctor.prototype); // 一步到位:空对象 + 连原型
  const res = Ctor.apply(obj, args);
  return (res !== null && typeof res === 'object') ? res : obj;
}

Q2:instanceof 的原理是什么?

判断构造函数的 prototype 是否出现在实例的原型链上(从 obj.__proto__ 一路查到 null)。

Q3:p.hasOwnProperty('x')'x' in p 的区别?

前者只看自身属性;后者看整条原型链(含继承来的属性)。

Q4:下面的输出是什么?

js 复制代码
function A() {}
A.prototype.x = 1;
const a = new A();
a.x = 2;
console.log(a.x);      // 2   ------ 自身遮蔽原型
delete a.x;
console.log(a.x);      // 1   ------ 自身删除后沿链找到原型
console.log(a.hasOwnProperty('x')); // false ------ 现在 x 在原型上

Q5:为什么 ObjectFunction 互为"祖先"?

js 复制代码
Function.prototype.__proto__ === Object.prototype; // true ------ 函数最终也是对象
Object.__proto__ === Function.prototype;           // true ------ Object 本身也是个函数
// 妙处:Function.prototype 是函数,但它没有 prototype 指向自己(Function.prototype 也是对象)

记忆法:"万物皆对象,函数也是对象" 。所以 Function.prototype__proto__ 指向 Object.prototype;而 Object 作为函数,它的 __proto__ 指向 Function.prototype。二者互相"认亲",但不构成循环(终点仍是 null)。


11. 一页速记表(面试前 30 秒扫一遍)

问题 答案
原型链是什么? 对象通过 __proto__ 串联起来、用于属性查找的链
谁有 prototype 只有函数(箭头函数除外)
谁有 __proto__ 所有对象(含函数)
实例怎么连上原型? new 时执行 obj.__proto__ = Ctor.prototype
属性查找顺序? 自身 → __proto__ → ... → Object.prototypenullundefined
链的终点? Object.prototype.__proto__ === null
instanceof 原理? Ctor.prototype 是否在 obj 原型链上
class extends 本质? 设置 Student.prototype.__proto__ = Person.prototype
如何避开 __proto__ Object.getPrototypeOf(obj) / Object.create(proto)

结语

原型链不是玄学,它只是一条**"属性找不到就往上级借"的查找链**,外加"函数通过 prototype 把方法共享给所有实例"这个设计。

下次再遇到原型题,别背结论,先在脑中画出那条链: 实例 → 构造函数的 prototype → 再往上 → ... → Object.prototype → null,然后在链上"走一遍属性查找",答案自然浮出水面。

附赠一句口诀:"函数有 prototype 来发方法,实例用 proto 来找方法,原型的 constructor 认回函数。"

相关推荐
工具派1 小时前
markdown在线编辑器怎么选?渲染管线的3个坑和md转PDF跑版记录
前端·后端
平头哥技术团队1 小时前
Day 13 | 调 line-height 和 margin:三处间距让名片页脱离模板感
开发语言·前端·javascript·学习·html5
一位正在转型AI全栈的前端工程师1 小时前
AI 全栈学习之旅 -Week 9:什么是AI Agent?从Function Calling到LangGraph
前端·python
计算机魔术师1 小时前
Claude Opus 5 干不过人类客服?23.9% 的通过率撕开 Agent 真相
前端
cjy0001112 小时前
2026年9月零基础能听懂国内 FDE 讲师的课吗?
大数据·前端·人工智能·fde
南雨北斗2 小时前
wangeditor5 在vue3项目中的正确配置
前端
老三_Micheal2 小时前
一文搞懂 Axios 请求参数详解:headers、params 与 body 的区别和使用场景
javascript
Cache技术分享2 小时前
517. Java 方法句柄 - 方法句柄 vs 反射 API
前端·后端
小刘在重生~2 小时前
Web 前端基础|HTML+CSS+JavaScript+Bootstrap
前端·css·html