news 2026/4/17 18:27:46

聚焦 “原型链与继承”

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
聚焦 “原型链与继承”

一、开篇直击:为什么原型链是 JS 的 “遗传密码”?

你是否有过这些困惑:

  • 为什么 [] instanceof Array 是 true,{} instanceof Object 也是 true?
  • 为什么给 Array.prototype 添加方法,所有数组实例都能直接调用?
  • Vue 实例的 $mount、$emit 方法,到底存在哪里?
  • 面试时被问 “手动实现继承”,却只能说出class extends,讲不清底层原理?

这些问题的答案,都指向 JS 的核心机制 ——原型链(Prototype Chain)。它不是语法糖,而是 JS 实现 “继承” 的底层逻辑,更是理解框架源码、写出优雅面向对象代码的关键。掌握原型链,才算真正打通 JS 的 “任督二脉”。

二、原型链的本质:3 个核心概念 + 1 条查找规则 + 内存模型

1. 先厘清 3 个易混淆概念(90% 的人在这里栽跟头)

概念

定义

关联关系

构造函数(Constructor)

用于创建对象的函数(如 function Person() {}、Array、Object)

构造函数有 prototype 属性

原型对象(Prototype)

构造函数的 prototype 属性指向的对象,包含实例共享的方法和属性

原型对象有 constructor 属性,指向构造函数

实例(Instance)

通过构造函数创建的对象(如 new Person()、[]、{})

实例有 __proto__ 属性,指向原型对象

核心公式(记死!)

实例.__proto__ === 构造函数.prototype

构造函数.prototype.constructor === 构造函数

实例.constructor === 构造函数(通过原型链继承而来)

2. 可视化案例:原型链的结构

// 1. 定义构造函数

function Person(name) {

this.name = name; // 实例私有属性

}

// 2. 给原型对象添加共享方法

Person.prototype.sayHi = function() {

console.log(`Hi, ${this.name}`);

};

// 3. 创建实例

const zhangsan = new Person("张三");

// 验证关联关系

console.log(zhangsan.__proto__ === Person.prototype); // true

console.log(Person.prototype.constructor === Person); // true

console.log(zhangsan.constructor === Person); // true

原型链结构图示(文字版)

zhangsan(实例)→ __proto__ → Person.prototype(原型对象)→ __proto__ → Object.prototype(顶层原型)→ __proto__ → null(原型链终点)

3. 原型链的核心作用:属性查找规则

当访问一个对象的属性 / 方法时,JS 会按以下顺序查找:

  1. 先在对象自身查找(如 zhangsan.name);
  1. 若找不到,沿 __proto__ 向上查找原型对象(如 zhangsan.sayHi() 来自 Person.prototype);
  1. 若仍找不到,继续沿原型链向上查找,直到 Object.prototype;
  1. 若 Object.prototype 中仍没有,返回 undefined。

示例验证

console.log(zhangsan.toString()); // "[object Object]"

// 查找路径:zhangsan → 无toString → Person.prototype → 无toString → Object.prototype → 有toString

4. 底层补充:原型链的内存模型(95 分关键)

很多人只懂 “表面关联”,却不懂内存分配逻辑 —— 这是进阶高级工程师的核心差距:

  • 实例的内存结构:每个实例仅存储 “自身私有属性”(如 zhangsan.name),原型对象的方法 / 属性(如 sayHi)不占用实例内存,仅通过 __proto__ 指针引用;
  • 原型对象的内存特性:所有实例共享同一个原型对象的内存地址,因此修改原型对象的方法,所有实例都会 “实时感知”(如 Person.prototype.sayHi = () => {} 会影响所有 Person 实例);
  • 内存释放条件:只有当 “实例被销毁” 且 “原型对象无其他引用” 时,原型对象才会被垃圾回收(GC)—— 这也是原型链可能导致内存泄漏的核心原因(如全局变量引用实例,实例引用原型对象)。

三、原型链的核心应用:JS 继承的 6 种实现方案(从基础到最优)

JS 本身没有 “类”(ES6 class 是语法糖,底层仍基于原型链),继承本质是 “原型链的复用”。以下是从基础到工业级的实现方案,附优缺点和实战选择:

1. 原型链继承(基础版)

// 父构造函数

function Parent() {

this.name = "父类";

}

Parent.prototype.getName = function() {

return this.name;

};

// 子构造函数

function Child() {}

Child.prototype = new Parent(); // 核心:子原型指向父实例

Child.prototype.constructor = Child; // 修复constructor指向

const child = new Child();

console.log(child.getName()); // "父类"(继承成功)

优点:简单直观,实现了原型方法复用;

缺点:父类私有属性会被所有子类实例共享(如 Parent 有数组属性,子类实例修改会相互影响);无法给父构造函数传参。

2. 构造函数继承(解决传参问题)

function Parent(name) {

this.name = name;

}

function Child(name) {

Parent.call(this, name); // 核心:调用父构造函数,绑定this

}

const child1 = new Child("张三");

const child2 = new Child("李四");

console.log(child1.name); // "张三"(不共享)

优点:父类私有属性不共享,支持给父构造函数传参;

缺点:原型方法无法继承(child1.getName() 会报错),方法只能定义在构造函数内,造成内存浪费。

3. 组合继承(原型链 + 构造函数,常用基础版)

function Parent(name) {

this.name = name;

}

Parent.prototype.getName = function() {

return this.name;

};

function Child(name, age) {

Parent.call(this, name); // 构造函数继承:私有属性

this.age = age;

}

Child.prototype = new Parent(); // 原型链继承:共享方法

Child.prototype.constructor = Child;

Child.prototype.getAge = function() {

return this.age;

};

const child = new Child("张三", 20);

console.log(child.getName()); // "张三"(继承原型方法)

console.log(child.age); // 20(私有属性)

优点:兼顾原型方法复用和私有属性独立,支持传参;

缺点:父构造函数会被调用两次(new Parent() 和 Parent.call()),造成不必要的性能开销。

4. 寄生组合继承(最优方案,框架源码常用)

解决组合继承的性能问题,核心是 “用父原型的副本替代父实例”:

function Parent(name) {

this.name = name;

}

Parent.prototype.getName = function() {

return this.name;

};

function Child(name, age) {

Parent.call(this, name); // 仅调用一次父构造函数

this.age = age;

}

// 核心:创建父原型的空对象副本(避免调用父构造函数)

Child.prototype = Object.create(Parent.prototype);

Child.prototype.constructor = Child; // 修复constructor

Child.prototype.getAge = function() {

return this.age;

};

优点:父构造函数仅调用一次,性能最优;兼顾所有优点,是工业级实现方案;

应用:Vue 源码中组件继承、React 早期的createClass继承,均基于此方案。

5. ES6 class 继承(语法糖,推荐实战使用)

class Parent {

constructor(name) {

this.name = name;

}

getName() { // 原型方法

return this.name;

}

static staticMethod() { // 静态方法(继承自类本身)

return "静态方法";

}

}

class Child extends Parent { // 核心:extends关键字

constructor(name, age) {

super(name); // 必须调用super,相当于Parent.call(this, name)

this.age = age;

}

getAge() {

return this.age;

}

}

const child = new Child("张三", 20);

console.log(child.getName()); // "张三"

console.log(Child.staticMethod()); // "静态方法"(静态继承)

本质:class + extends 是寄生组合继承的语法糖,底层仍基于原型链;

优点:语法简洁,支持静态方法继承,符合面向对象编程习惯;

实战选择:日常开发优先使用 ES6 class,需理解底层原理时参考寄生组合继承。

6. 混入继承(多继承场景)

JS 不支持多继承,但可通过 “混入(Mixin)” 实现多原型复用:

const Mixin1 = {

method1() { console.log("混入方法1"); }

};

const Mixin2 = {

method2() { console.log("混入方法2"); }

};

// 给Child原型添加混入方法

Object.assign(Child.prototype, Mixin1, Mixin2);

const child = new Child();

child.method1(); // "混入方法1"

child.method2(); // "混入方法2"

应用:Vue 的mixins选项、React 的HOC(高阶组件),本质是混入继承的延伸。

四、ES6 class 进阶:你不知道的底层细节(提分关键)

很多人用class却不懂其底层特性,这部分是面试高频加分项:

1. super 的双重角色
  • 角色 1:作为函数:super(name) 相当于 Parent.call(this, name),必须在constructor内第一行调用(确保 this 绑定正确);
  • 角色 2:作为对象:super.getName() 相当于 Parent.prototype.getName.call(this),可访问父类原型方法;
  • 注意:在静态方法中,super 指向父类本身(如 super.staticMethod() 等价于 Parent.staticMethod())。
2. 私有字段与原型链的关系

ES6 新增的私有字段(# 前缀)不参与原型链继承,仅属于实例自身:

class Parent {

#privateField = "私有属性"; // 私有字段

getPrivate() {

return this.#privateField;

}

}

class Child extends Parent {}

const child = new Child();

console.log(child.getPrivate()); // "私有属性"(通过父类方法访问)

console.log(child.#privateField); // 报错:私有字段不可直接访问

console.log(Child.prototype.#privateField); // 报错:私有字段不在原型上

核心逻辑:私有字段存储在实例的 “私有槽位” 中,原型链无法访问,避免了原型链共享的问题。

3. 静态字段的继承原理

静态字段(static 关键字)存储在构造函数上,而非原型对象上,继承本质是 “子类构造函数引用父类静态字段”:

class Parent {

static staticField = "静态字段";

}

class Child extends Parent {}

console.log(Child.staticField); // "静态字段"(继承自Parent)

console.log(Child.prototype.staticField); // undefined(不在原型上)

底层逻辑:Child.staticField 是通过 Child.__proto__ = Parent 实现的 —— 子类构造函数的__proto__指向父类构造函数,因此能访问父类静态属性。

五、框架源码实战:原型链的工业级应用

1. Vue3 组件继承的底层实现

Vue3 的defineComponent本质是基于原型链的封装,组件的methods、computed等最终会挂载到组件实例的原型上:

// Vue3源码简化逻辑

function defineComponent(options) {

const Component = function() {};

// 原型链复用:将options.methods挂载到组件原型

Object.assign(Component.prototype, options.methods);

// 继承Vue内置方法(如$emit、$mount)

Component.prototype.__proto__ = Vue.prototype;

return Component;

}

// 组件使用

const MyComponent = defineComponent({

methods: {

handleClick() { console.log("点击"); }

}

});

const instance = new MyComponent();

instance.handleClick(); // 原型链查找:MyComponent.prototype → 存在

instance.$emit(); // 原型链查找:MyComponent.prototype → Vue.prototype → 存在

2. React 组件的原型链设计

React 的Component类是所有类组件的父类,底层基于 ES6 class继承,原型链结构如下:

MyComponent实例 → MyComponent.prototype → React.Component.prototype → Object.prototype → null

React 的生命周期方法(如componentDidMount)均定义在React.Component.prototype上,因此所有子类组件都能继承使用。

六、原型链的 “坑”:90% 开发者踩过的 5 个误区 + 进阶边界场景

1. 误区 1:__proto__ 与 prototype 混用
  • 错误认知:认为实例有 prototype 属性,构造函数有 __proto__ 属性;
  • 正确结论:只有构造函数(含Function)有 prototype;只有实例(含函数实例)有 __proto__;
  • 例外:Function.prototype 是函数实例,但没有 prototype 属性(避免无限递归)。
2. 误区 2:修改原型对象后,已有实例失效

function Person() {}

const p1 = new Person();

// 错误写法:直接替换原型对象(已有实例的__proto__仍指向旧原型)

Person.prototype = { sayHi: () => {} };

console.log(p1.sayHi()); // 报错:sayHi is not a function

// 正确写法:修改原型对象的属性(不替换整个对象)

Person.prototype.sayHi = () => {};

console.log(p1.sayHi()); // 正常执行

3. 误区 3:instanceof 检测的是 “构造函数”,而非 “原型链”
  • instanceof原理:检测构造函数的 prototype 是否在实例的原型链上;
  • 示例

console.log([] instanceof Array); // true(Array.prototype在[]的原型链上)

console.log([] instanceof Object); // true(Object.prototype在[]的原型链上)

console.log(Array instanceof Function); // true(Function.prototype在Array的原型链上)

4. 误区 4:原型链继承中,父类引用类型属性被共享

function Parent() {

this.hobbies = ["篮球"]; // 引用类型属性

}

function Child() {}

Child.prototype = new Parent();

const child1 = new Child();

const child2 = new Child();

child1.hobbies.push("足球");

console.log(child2.hobbies); // ["篮球", "足球"](意外共享)

解决方案:用构造函数继承或组合继承,将引用类型属性定义在构造函数内。

5. 误区 5:ES6 class 没有原型链
  • 错误认知:class 是 “真正的类”,与原型链无关;
  • 正确结论:class 是语法糖,Child extends Parent 本质是 Child.prototype.__proto__ = Parent.prototype,仍基于原型链实现继承。
6. 进阶边界场景:null 原型对象与原型链污染
  • 场景 1:创建无原型对象:const obj = Object.create(null),此时 obj.__proto__ === undefined,原型链终点为null,不继承Object.prototype的任何方法(如toString、hasOwnProperty),适合作为纯净的字典对象;
  • 场景 2:原型链污染(安全风险):恶意修改原型对象的属性,会影响所有实例:

// 原型链污染攻击

Object.prototype.__proto__.malicious = "恶意属性";

const obj = {};

console.log(obj.malicious); // "恶意属性"(所有对象都被污染)

防护方案

  1. 避免直接修改 Object.prototype;
  1. 使用 hasOwnProperty 检测属性是否为对象自身属性(obj.hasOwnProperty('malicious'));
  1. 用 Object.create(null) 创建纯净对象,避免继承原型链属性。

七、面试高频考点:进阶真题解析(95 分必备)

真题 1:写出以下代码的输出结果(原型链 + 闭包结合)

function Parent() {

this.x = 100;

}

Parent.prototype.getX = function() {

return this.x;

};

function Child() {

Parent.call(this);

this.x = 200;

return {

x: 300,

getX: function() {

return this.x;

}

};

}

Child.prototype = new Parent();

Child.prototype.constructor = Child;

const child = new Child();

console.log(child.getX()); // 300(返回的对象自身有getX方法)

console.log(child.__proto__.getX.call(child)); // 100(Child.prototype的getX,this指向child返回的对象,x=300?不!这里易错:)

// 正确解析:

// 1. child是Child构造函数返回的对象({x:300, getX: ...}),其__proto__是Object.prototype(而非Child.prototype);

// 2. child.__proto__.getX 不存在,沿原型链查找Object.prototype也没有,会报错?不!再看:

// 3. Child.prototype是new Parent()创建的实例,有getX方法;但child的__proto__是Object.prototype,因此child.__proto__.getX 是undefined;

// 正确输出:child.getX() → 300;child.__proto__.getX → undefined(报错);

// 核心坑:构造函数返回对象时,实例的__proto__不再指向构造函数的prototype,而是Object.prototype。

真题 2:手动实现 ES6 class 的继承(含静态方法、super)

function myExtends(Child, Parent) {

// 1. 继承原型方法(寄生组合继承核心)

Child.prototype = Object.create(Parent.prototype);

Child.prototype.constructor = Child;

// 2. 继承静态方法(子类构造函数的__proto__指向父类构造函数)

Child.__proto__ = Parent;

// 3. 实现super(挂载到Child.prototype上)

Child.prototype.super = Parent;

}

// 使用示例

function Parent(name) {

this.name = name;

}

Parent.staticMethod = function() {

return "静态方法";

};

Parent.prototype.getName = function() {

return this.name;

};

function Child(name, age) {

this.super(name); // 相当于super(name)

this.age = age;

}

myExtends(Child, Parent);

Child.prototype.getAge = function() {

return this.age;

};

// 验证

const child = new Child("张三", 20);

console.log(child.getName()); // "张三"

console.log(Child.staticMethod()); // "静态方法"(继承静态方法)

真题 3:解释 Function.__proto__ === Function.prototype 的原因

答案核心:Function 是构造函数,同时也是函数实例 —— 所有函数实例的 __proto__ 都指向 Function.prototype,Function 作为函数实例,自然也遵循这一规则(避免原型链无限递归的特殊设计)。

延伸:Object.__proto__ === Function.prototype(因为 Object 是构造函数,属于函数实例),而 Function.prototype.__proto__ === Object.prototype(原型链的顶层关联)。

八、总结:原型链的 “道” 与 “术”

  • :原型链是 JS 的底层机制,是 “对象复用” 的核心,ES6 class 只是其语法糖;
    1. 日常开发用 class + extends 写继承(简洁高效);
    1. 看懂框架源码时,要能识别寄生组合继承、混入继承的本质;
    1. 避坑关键:分清 __proto__ 与 prototype,理解原型链查找规则,警惕原型链污染;
  • 终极认知:JS 中 “一切皆对象”,而对象的 “遗传关系” 由原型链定义 —— 掌握原型链,才能真正理解 JS 的面向对象设计思想,看懂 Vue、React 等框架的底层实现。

原型链看似抽象,但只要抓住 “实例→原型对象→顶层原型” 的核心逻辑,结合内存模型、框架源码和进阶场景拆解,就能彻底掌握。它不仅是面试的 “加分项”,更是成为高级前端工程师的 “必修课”。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 3:33:35

从零打造你的微信AI伴侣:14天智能对话体验指南

从零打造你的微信AI伴侣:14天智能对话体验指南 【免费下载链接】WeChatBot_WXAUTO_SE 将deepseek接入微信实现自动聊天的聊天机器人。本项目通过wxauto实现收发微信消息。原项目仓库:https://github.com/umaru-233/My-Dream-Moments 本项目由iwyxdxl在原…

作者头像 李华
网站建设 2026/4/15 9:49:01

Obsidian字体优化:让你的笔记阅读体验焕新升级

Obsidian字体优化:让你的笔记阅读体验焕新升级 【免费下载链接】awesome-obsidian 🕶️ Awesome stuff for Obsidian 项目地址: https://gitcode.com/gh_mirrors/aw/awesome-obsidian 你是否在使用Obsidian时感觉眼睛容易疲劳?或者觉得…

作者头像 李华
网站建设 2026/4/17 3:09:15

Bloxstrap启动器深度配置与优化指南

Bloxstrap启动器深度配置与优化指南 【免费下载链接】bloxstrap An open-source, feature-packed alternative bootstrapper for Roblox. 项目地址: https://gitcode.com/GitHub_Trending/bl/bloxstrap 前言:为什么选择Bloxstrap? 如果你对Roblo…

作者头像 李华
网站建设 2026/4/15 3:25:08

微信小程序开发调用云函数转发IndexTTS2语音请求

微信小程序通过云函数调用IndexTTS2实现语音合成的技术实践 在智能语音日益普及的今天,越来越多的小程序开始尝试集成“文字转语音”功能——无论是为视障用户提供无障碍阅读支持,还是让智能家居面板能“开口说话”。然而,直接在前端运行高质…

作者头像 李华
网站建设 2026/4/17 8:14:21

解决GitHub下载慢问题,IndexTTS2模型镜像加速通道上线

解决GitHub下载慢问题,IndexTTS2模型镜像加速通道上线 在AI语音技术飞速发展的今天,越来越多的开发者开始尝试部署高质量的文本到语音(Text-to-Speech, TTS)系统。然而,一个令人头疼的问题始终存在:从GitHu…

作者头像 李华