宁德市网站建设_网站建设公司_论坛网站_seo优化
2026/1/21 15:52:56 网站建设 项目流程

在 JavaScript 中,继承是面向对象编程(OOP)的核心特性之一,但与 Java、C# 等传统面向对象语言不同,JS 并没有原生的类继承机制,其继承本质是基于原型链(prototype chain)实现的。

从 ES5 手动封装继承逻辑,到 ES6 引入classextends语法糖,JS 继承方案不断演进。本文将从基础到进阶,逐一拆解七种常用继承方式,分析其优劣及适用场景,帮你彻底搞懂 JS 继承的底层逻辑与实战用法。

一、原型链继承(最基础的继承方案)

核心思路

将父类的实例赋值给子类的原型对象,使子类实例能通过原型链访问父类的实例属性和原型方法,这是 JS 继承的最底层逻辑。

实现代码

// 父类:定义基础属性和原型方法 function Parent() { this.parentProp = "父类实例属性"; this.commonArr = [1, 2, 3]; // 引用类型属性 } Parent.prototype.parentMethod = function() { console.log("父类原型方法,可被所有实例复用"); }; // 子类:定义自身实例属性 function Child() { this.childProp = "子类实例属性"; } // 核心:子类原型指向父类实例,搭建原型链 Child.prototype = new Parent(); // 修正 constructor 指向(关键步骤,避免原型紊乱) Child.prototype.constructor = Child; // 测试 const child1 = new Child(); const child2 = new Child(); child1.commonArr.push(4); console.log(child1.parentProp); // 父类实例属性 child1.parentMethod(); // 父类原型方法,可被所有实例复用 console.log(child1.commonArr); // [1,2,3,4] console.log(child2.commonArr); // [1,2,3,4](引用类型被共享)

优缺点分析

优点:实现简单,能自然继承父类原型上的方法(实现方法复用)。

缺点

  • 父类的引用类型属性会被所有子类实例共享,一个实例修改会影响其他实例;

  • 创建子类实例时,无法向父类构造函数传递参数,灵活性差。

二、构造函数继承(借用构造函数方案)

核心思路

在子类构造函数内部,通过call()apply()方法借用父类构造函数,强制将父类的实例属性绑定到子类实例上,解决原型链继承中引用类型共享的问题。

实现代码

// 父类:支持接收参数 function Parent(name) { this.name = name; this.hobbies = ["读书", "运动"]; // 引用类型属性 } // 父类原型方法 Parent.prototype.sayName = function() { console.log(this.name); }; // 子类:借用父类构造函数 function Child(name, age) { // 核心:改变父类构造函数的 this 指向,传递参数 Parent.call(this, name); this.age = age; // 子类自身属性 } // 测试 const child1 = new Child("小明", 18); const child2 = new Child("小红", 20); child1.hobbies.push("画画"); console.log(child1.hobbies); // [1,2,3,4] console.log(child2.hobbies); // [1,2,3](引用类型独立) console.log(child1.name); // 小明(成功传参) child1.sayName(); // 报错:child1.sayName is not a function(无法继承原型方法)

优缺点分析

优点

  • 解决了引用类型属性共享的问题,每个子类实例属性独立;

  • 支持向父类构造函数传参,灵活性提升。

缺点:无法继承父类原型上的方法,导致方法无法复用,每个子类实例需重复定义方法,浪费内存。

三、组合继承(原型链+构造函数混合方案)

核心思路

结合原型链继承和构造函数继承的优点:用构造函数继承父类实例属性(保证独立性和传参),用原型链继承父类原型方法(实现方法复用),是 ES5 中最常用的经典继承方案。

实现代码

// 父类 function Parent(name) { this.name = name; this.hobbies = ["读书", "运动"]; } Parent.prototype.sayName = function() { console.log(`我的名字是 ${this.name}`); }; // 子类 function Child(name, age) { // 1. 构造函数继承:实例属性独立,支持传参 Parent.call(this, name); this.age = age; } // 2. 原型链继承:继承父类原型方法,实现复用 Child.prototype = new Parent(); Child.prototype.constructor = Child; // 子类自身原型方法 Child.prototype.sayAge = function() { console.log(`我的年龄是 ${this.age}`); }; // 测试 const child = new Child("小明", 18); child.sayName(); // 我的名字是 小明 child.sayAge(); // 我的年龄是 18 child.hobbies.push("画画"); console.log(child.hobbies); // ["读书", "运动", "画画"] const child2 = new Child("小红", 20); console.log(child2.hobbies); // ["读书", "运动"](独立不影响)

优缺点分析

优点:兼顾了方法复用和实例属性独立,支持传参,满足大部分 ES5 场景需求。

缺点:父类构造函数会被调用两次(一次是new Parent()赋值给子类原型,一次是Parent.call()),存在轻微性能损耗,且父类实例属性会在子类原型上重复定义。

四、原型式继承(基于现有对象创建新对象)

核心思路

无需定义构造函数,直接基于现有对象创建新对象,本质是对原型链继承的简化封装。ES5 中的Object.create()方法就是该思路的原生实现。

实现代码

// 模拟 Object.create 底层逻辑(简易版) function createObj(proto) { function F() {} // 空构造函数 F.prototype = proto; // 原型指向传入对象 return new F(); // 返回新实例 } // 父对象(无需定义构造函数) const parent = { name: "父对象", hobbies: ["读书", "运动"], sayName: function() { console.log(this.name); } }; // 基于父对象创建子类实例 const child1 = createObj(parent); const child2 = Object.create(parent); // 原生方法实现 child1.name = "子对象1"; child1.hobbies.push("画画"); console.log(child1.name); // 子对象1(自身属性) console.log(child2.name); // 父对象(未修改,继承原型属性) console.log(child1.hobbies); // ["读书", "运动", "画画"] console.log(child2.hobbies); // ["读书", "运动", "画画"](引用类型共享) child1.sayName(); // 子对象1(继承原型方法)

优缺点分析

优点:语法简洁,无需定义构造函数,适合快速基于现有对象扩展新对象。

缺点:引用类型属性仍会被共享,且无法向父对象传递参数,灵活性不足。

五、寄生式继承(原型式继承增强版)

核心思路

在原型式继承的基础上,封装一个创建对象的函数,在函数内部为新对象添加额外的属性和方法,实现对象功能增强,本质是对原型式继承的补充。

实现代码

// 封装寄生式继承函数 function createEnhancedObj(proto) { // 1. 原型式继承创建基础对象 const newObj = Object.create(proto); // 2. 增强对象:添加自定义属性和方法 newObj.age = 18; newObj.sayAge = function() { console.log(`我的年龄是 ${this.age}`); }; return newObj; } // 父对象 const parent = { name: "父对象", sayName: function() { console.log(this.name); } }; // 创建增强后的子类实例 const child = createEnhancedObj(parent); // 测试 child.sayName(); // 父对象 child.sayAge(); // 我的年龄是 18 console.log(child.age); // 18

优缺点分析

优点:在原型式继承的基础上增强了对象功能,灵活性更高。

缺点:新增的方法无法复用(每个实例都有独立的方法副本),引用类型属性共享问题仍未解决。

六、寄生组合式继承(ES5 最优继承方案)

核心思路

针对组合继承的缺点(父类构造函数调用两次)进行优化,通过寄生方式继承父类原型(而非父类实例),仅调用一次父类构造函数,兼顾性能、复用性和独立性,是 ES5 中最完美的继承方案。

实现代码

// 父类 function Parent(name) { this.name = name; this.hobbies = ["读书", "运动"]; } Parent.prototype.sayName = function() { console.log(`我的名字是 ${this.name}`); }; // 子类 function Child(name, age) { // 仅调用一次父类构造函数,获取实例属性 Parent.call(this, name); this.age = age; } // 核心:寄生式继承父类原型,避免调用父类构造函数 Child.prototype = Object.create(Parent.prototype); // 修正 constructor 指向 Child.prototype.constructor = Child; // 子类原型方法 Child.prototype.sayAge = function() { console.log(`我的年龄是 ${this.age}`); }; // 测试 const child = new Child("小明", 18); child.sayName(); // 我的名字是 小明 child.sayAge(); // 我的年龄是 18 child.hobbies.push("画画"); console.log(child.hobbies); // ["读书", "运动", "画画"] const child2 = new Child("小红", 20); console.log(child2.hobbies); // ["读书", "运动"](独立不影响)

优缺点分析

优点

  • 仅调用一次父类构造函数,无性能损耗;

  • 继承父类实例属性(独立、支持传参)和原型方法(复用);

  • 无冗余属性,原型链清晰,是 ES5 实战首选。

缺点:实现逻辑比组合继承稍复杂,需手动处理原型和 constructor 指向。

七、ES6 Class 继承(语法糖方案)

核心思路

ES6 引入classextendssuper关键字,本质是寄生组合式继承的语法糖,简化了继承的写法,让 JS 继承更贴近传统面向对象语言的风格,底层逻辑仍基于原型链。

实现代码

// 父类(class 语法定义) class Parent { // 构造函数:接收参数 constructor(name) { this.name = name; this.hobbies = ["读书", "运动"]; } // 原型方法(无需绑定到 prototype) sayName() { console.log(`我的名字是 ${this.name}`); } } // 子类:通过 extends 继承父类 class Child extends Parent { constructor(name, age) { // 调用父类构造函数,必须在 this 之前 super(name); this.age = age; // 子类自身属性 } // 子类方法 sayAge() { console.log(`我的年龄是 ${this.age}`); } } // 测试 const child = new Child("小明", 18); child.sayName(); // 我的名字是 小明 child.sayAge(); // 我的年龄是 18 child.hobbies.push("画画"); console.log(child.hobbies); // ["读书", "运动", "画画"] const child2 = new Child("小红", 20); console.log(child2.hobbies); // ["读书", "运动"](独立不影响)

补充说明

1.super关键字的作用:在子类构造函数中调用父类构造函数(super(name)),也可在子类方法中调用父类方法(super.sayName());

2. ES6 Class 继承支持静态方法(static修饰),静态方法可通过类名直接调用,不会被实例继承,且能被子类继承;

3. 兼容性:现代浏览器和 Node.js 均支持,如需兼容 IE 需通过 Babel 转译。

总结:不同场景下的选择建议

核心结论:实际开发中,优先使用 ES6class + extends(简洁、规范、无性能问题);如需兼容 ES5 环境,选用寄生组合式继承(最优性能)。

继承方式

适用场景

核心优势

核心不足

原型链继承

简单demo、基础理解原型链

实现简单、方法复用

引用类型共享、无法传参

构造函数继承

需独立实例属性、无需继承原型方法

属性独立、支持传参

无法继承原型方法、方法无法复用

组合继承

ES5 基础开发场景

兼顾复用与独立、支持传参

父类构造函数调用两次

原型式/寄生式继承

基于现有对象快速扩展

语法简洁、无需定义构造函数

引用类型共享、方法无法复用

寄生组合式继承

ES5 高性能场景、框架底层

性能最优、无冗余属性

实现逻辑稍复杂

ES6 Class 继承

现代开发、工程化项目

语法规范、简洁易维护

需兼容处理旧环境

掌握 JS 继承的核心是理解原型链的工作原理,无论哪种继承方式,本质都是通过调整原型链的指向,实现属性和方法的复用与扩展。希望本文能帮你理清思路,在实战中灵活选择合适的继承方案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询