JavaScript中的继承方式详细解析

news2025/2/25 12:05:59

什么是继承

继承是面向对象编程中的一个重要概念,它指的是一个对象(或类)可以获得另一个对象(或类)的属性和方法。在继承中,被继承的对象通常称为父类(或基类、超类),继承属性和方法的对象称为子类(或派生类、衍生类)。

继承的主要作用包括:

  1. 代码复用:通过继承,子类可以重用父类的属性和方法,避免了重复编写相似的代码,提高了代码的可维护性和复用性。

  2. 组织代码:通过将相关的属性和方法封装在一个类中,并让其他类继承它,可以更好地组织和管理代码,使代码结构更清晰。

  3. 多态:继承是实现多态的基础。子类可以重写父类的方法,从而根据实际情况执行不同的代码逻辑,实现不同的行为。

虽然JavaScript并不是真正的面向对象语言,但是它天生的灵活性,使应用场景更加丰富。

关于继承,我们举个形象的例子:

// 父类 Animal
class Animal {
    constructor(name) {
        this.name = name;
    }

    // 父类方法
    speak() {
        console.log(`${this.name} makes a noise.`);
    }
}

// 子类 Dog 继承自 Animal
class Dog extends Animal {
    constructor(name) {
        super(name); // 调用父类的构造函数
    }

    // 子类方法
    speak() {
        console.log(`${this.name} barks.`);
    }
}

// 创建一个 Animal 实例
const animal = new Animal('Generic Animal');
animal.speak(); // 输出: Generic Animal makes a noise.

// 创建一个 Dog 实例
const dog = new Dog('Buddy');
dog.speak(); // 输出: Buddy barks.

在这个例子中,Animal定义了一个通用的动物类它有一个构造函数和一个speak()方法,Dog类通过extends关键字继承了Animal类。它也有一个构造函数和一个speak()方法,但是speak()方法被重写,输出了不同的声音。在子类的构造函数中使用super()调用了父类的构造函数,以确保在创建子类实例时父类的属性也被正确的初始化。

继承方式

  • 原型链继承
  • 构造函数继承(借助call)
  • 组合继承
  • 原型式继承
  • 寄生虫式继承
  • 寄生组合式继承

原型链继承

原型链继承是比较常用的继承方式之一,其中涉及的构造函数、原型、实例。三者之间存在一定的关系,即每个构造函数都有一个圆形对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针

function Parent() {
  this.name = 'parent1';
  this.play = [1, 2, 3]
}
function Child() {
  this.type = 'child2';
}
Child.prototype = new Parent();

var s1 = new Child2();
var s2 = new Child2();
s1.play.push(4);
console.log(s1.play, s2.play); // [1,2,3,4]

我们发现,在改变s1的play属性的时候,s2也跟着变化了,这是因为两个实例使用的是同一个原型对象,内存空间也是共享的。

原型链继承小结:

优点:

  • 简单易懂:实现简单,易于理解
  • 可以实现函数复用:子类可以共享父类原型中的方法

缺点:

  • 共享属性:所有的实力共享父类原型中的属性,容易造成属性修改的相互影响
  • 无法向父类传参数:无法在创建子类实例的时候向父类构造函数传递参数

 构造函数继承

构造函数继承是通过在子类构造函数中借助call || bind等调用父类的构造函数来实现继承的模式。这种方式主要是基于JavaScript中的函数特性。

举例1:

// 父类
function Parent(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.sayName = function(){
    console.log(this.name);
}

// 子类
function Child(name, age) {
    Parent.call(this, name); // 在子类构造函数中调用父类构造函数,传入子类实例(this)和参数
    this.age = age;
}

// 创建子类实例
var child1 = new Child('John', 10);
var child2 = new Child('Jane', 12);

// 修改子类实例属性
child1.colors.push('black');

console.log(child1.name); // 输出: John
console.log(child1.age); // 输出: 10
console.log(child1.colors); // 输出: ["red", "blue", "green", "black"]

console.log(child2.name); // 输出: Jane
console.log(child2.age); // 输出: 12
console.log(child2.colors); // 输出: ["red", "blue", "green"]

child1.sayName()//报错

可以从上面代码中发现,父类原型上定义的方法,子类是无法继承这些方法的。相比于第一种原型链继承的方法,构造函数继承的方式的父类应用不会被共享,优化了第一种继承方式的弊端。但是缺点也很明显,即不能继承原型属性或者方法。

构造函数继承小结:

优点:

  • 可以向父类传递参数:子类可以通过在构造函数中调用父类构造函数来传递参数
  • 没有共享属性:每个实例都有自己的属性,不会互相影响

缺点:

  • 无法继承方法:子类无法继承父类原型中的方法,导致方法无法复用
  • 造成内存浪费:每个实例都会单独拥有一份方法的副本,造成内存浪费
  • 无法形成真正的原型链,导致无法使用原型链上的一些方法和属性

组合继承

组合继承结合了原型链继承和构造函数继承的优点,两者结合,避免了两者的缺点。通过组合继承,我们可以实现实例属性的独立性,同时实现方法的共享,提高了代码的可维护性和复用性。

// 父类
function Parent(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.sayName = function(){
    console.log(this.name);
}
// 子类
function Child(name, age) {
    Parent.call(this, name); // 在子类构造函数中调用父类构造函数,传入子类实例(this)和参数
    this.age = age;
}

Child.prototype = new Parent(); // 子类的原型指向父类的实例
Child.prototype.constructor = Child; // 修正子类的构造函数为自己

var s1 = new Child('hzz11', 18);
var s2 = new Child('hzz22',22);
console.log(s1)
console.log(s1.name); // hzz11
s1.colors.push('black');

console.log(s1.colors); // ["red", "blue", "green", "black"]
console.log(s2.colors); // ["red", "blue", "green"]
s1.sayName(); // hzz11
s2.sayName(); // hzz22

 这种方式看上去觉得还行,似乎没什么问题的样子。但是仔细观察你会发现,上面代码中的Parent执行了两次(Parent.call(this, name)、new Parent()),造成了多构造一次的性能开销。

组合式继承小结:

优点:

  • 结合了构造函数和原型链继承的优点:通过构造函数实现实例属性,通过原型链继承共享方法,既够传递参数,又能实现方法的复用
  • 方法共享:子类实例共享父类原型中的方法,节省内存

缺点:

  • 重复的调用构造函数:在创建子类实例时,会调用两次父类构造函数,一次是在原型链继承时,一次是在构造函数继承时,可能会导致一些不必要的性能开销。

原型式继承

原型式继承是通过浅复制一个对象来创建一个新的对象,并将新对象的原型指向这个被复制的对象。这样新对象就能够继承被复制对象的属性和方法

// 原型对象
var person = {
    name: 'John',
    age: 30,
    sayHello: function() {
        console.log('Hello, my name is ' + this.name);
    }
};

// 创建一个继承自 person 的新对象
var john = Object.create(person);

console.log(john.name); // 输出: John
console.log(john.age); // 输出: 30
john.sayHello(); // 输出: Hello, my name is John

Object.create()方法说明:Object.create()的作用是创建一个新的对象,并将该对象的原型链指向另外一个对象或者null

  1. 创建新对象Object.create() 可以创建一个新的对象,该对象继承了指定的原型对象的属性和方法。

  2. 指定原型链:通过指定一个对象作为参数,新创建的对象将会继承该对象的属性和方法,并且其原型链将指向这个对象。

  3. 继承属性和方法:新创建的对象将会继承指定对象的属性和方法,包括原型链上的属性和方法。

  4. 属性描述符继承:新创建的对象会继承指定对象的属性的属性描述符(比如可枚举、可写、可配置等)。

  5. 创建原型链终点:当传入 null 作为参数时,将创建一个没有原型链的对象,这个对象将不会继承任何属性和方法,相当于创建了原型链的终点。

语法:

Object.create(proto[, propertiesObject])

其中,proto 参数是新对象的原型对象,可以是 null 或者一个对象。propertiesObject 是可选参数,用于定义额外的属性,该参数的对象属性将被添加到新创建的对象中,属性描述符则将与 Object.defineProperty() 方法的功能一致。

下面是模拟Object.create()方法的实现:

function createObject (parent) {
  function F () { } // 创建一个临时构造函数
  F.prototype = parent; // 将临时构造函数的原型指向 parent 对象
  return new F(); // 返回新对象
}

原型式继承小结:

优点:

  • 简单灵活:使用方便,可以快速创建对象
  • 可以通过原型链并进行属性和方法的复用

缺点:

  • 共享引用类型属性:如果一个对象的引用类型属性被修改,会影响到所有继承自同一个原型的对象

寄生式继承

寄生式继承是在原型式继承的基础上增加了对象,返回一个增强后的对象。这种模式在原有对象的基础上添加额外的方法或者属性,从而实现继承

  • 原型式继承的基础:在原型式继承中,我们通过浅复制一个对象来创建一个新对象,新对象的原型链指向被复制的对象。这样,新对象就继承了被复制对象的属性和方法。
  • 增强对象:在寄生式继承中,我们不仅创建了一个新对象,还对这个新对象进行了增强。这个增强可以包括添加新的方法、修改已有方法等。
  • 返回增强后的对象:最后,我们将这个增强后的对象返回,以供使用者使用。
// 寄生式继承函数
function createChild(parent) {
    var child = Object.create(parent); // 基于原型式继承创建一个对象
    child.sayHello = function() {      // 增加额外的方法
        console.log('Hello from Child');
    };
    return child;                      // 返回增强后的对象
}

// 原型对象
var parent = {
    name: 'Parent',
    sayName: function() {
        console.log('My name is ' + this.name);
    }
};

// 创建一个继承自 parent 的新对象
var child = createChild(parent);

console.log(child.name); // 输出: Parent
child.sayName();         // 输出: My name is Parent
child.sayHello();        // 输出: Hello from Child

其优缺点也很明显,跟上面讲的原型式继承一样

寄生组合式继承

寄生组合式继承是JavaScript中一种高效的继承模式,它结合了寄生式和组合式继承的优点。寄生组合式继承的核心思想是在子类构造函数中调用父类构造函数,通过寄生式继承来继承父类的原型,以实现方法的共享和实力属性的对立性

// 父类
function Parent(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

// 父类原型方法
Parent.prototype.sayHello = function() {
    console.log('Hello from ' + this.name);
};

// 子类
function Child(name, age) {
    Parent.call(this, name); // 构造函数继承,继承实例属性
    this.age = age;
}

// 使用寄生式继承继承父类原型
function inheritPrototype(child, parent) {
    var prototype = Object.create(parent.prototype); // 创建父类原型的副本
    prototype.constructor = child; // 修正构造函数指向
    child.prototype = prototype; // 将子类原型指向父类原型的副本
}

// 将子类原型继承父类
inheritPrototype(Child, Parent);

// 创建子类实例
var child1 = new Child('John', 10);
var child2 = new Child('Jane', 12);

// 修改子类实例属性
child1.colors.push('black');

console.log(child1.name); // 输出: John
console.log(child1.age); // 输出: 10
console.log(child1.colors); // 输出: ["red", "blue", "green", "black"]

console.log(child2.name); // 输出: Jane
console.log(child2.age); // 输出: 12
console.log(child2.colors); // 输出: ["red", "blue", "green"]

child1.sayHello(); // 输出: Hello from John
child2.sayHello(); // 输出: Hello from Jane

在这个示例中,Parent 是父类构造函数,Child 是子类构造函数。通过 Parent.call(this, name) 实现了构造函数继承,继承了父类的实例属性。然后,通过 inheritPrototype(Child, Parent) 函数实现了寄生式继承,将子类原型继承父类原型的副本,从而实现了方法的共享。最后,我们创建了两个子类实例 child1child2,它们分别拥有独立的实例属性和共享的方法。

寄生组合式继承小结

优点:

  • 实现了方法的共享和实力属性的独立性,避免了构造函数继承和原型链继承的缺点
  • 避免了构造函数继承是调用两次父类构造函数的性能开销

extends继承

文章一开头,我们是使用ES6 中的extends关键字直接实现 JavaScript的继承,我们利用babel工具进行转换也可以发现,extends实际采用的也是寄生组合继承方法,因此也证明了这种方式是较优的解决继承的方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1528909.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

prince2和Pmp哪个含金量高?

一,认证情况 1、PMP(项目管理专业人士资格认证)是PMI( 美国项目管理协会 )最有影响力的证书,PMBOK( 项目管理知识体系指南 )是PMP的认证标准,是美国国家项目管理标准,PMI会员有70%集中在北美,PMP认证在北美地区很受欢…

基础乱炖来吧

1,SSH框架和SSM区别 SSH:structspringhibernate,SSM:MVCspringmybatis struct入口是filter级别,对action类进行请求,一个action类对应一个请求、类拦截;spring-mvcservlet级别,方法级别请求&…

【ESP32 IDF】ESPTIMER定时器

文章目录 前言一、ESPTIMER定时器的介绍1.1 定时器是什么1.2 ESPTIMER定时器的介绍 二、ESPTIMER的使用2.1 简单使用过程2.2 停止定时器2.3 删除定时器 三、示例代码总结 前言 在ESP32 IDF开发框架中,ESPTIMER是一个功能强大的定时器模块,用于实现定时任…

武汉星起航引领跨境电商风潮,五对一服务体系助力卖家飞跃成长

在全球贸易蓬勃发展的背景下,跨境电商行业迎来了前所未有的机遇与挑战。在这一领域中,武汉星起航电子商务有限公司以其专业的运营团队和优质的服务,成为众多跨境卖家的首选合作伙伴。 武汉星起航自2020年创立以来,始终致力于为跨…

前端项目ip做域名映射

1、在阿里云、腾讯云购买域名 SSL 证书和私钥生成 阿里云生成证书详解 /path/to/your/ssl_certificate1.crt和/path/to/your/ssl_certificate1.key 生成私钥(key): 使用 OpenSSL 工具生成一个私钥文件。执行以下命令: openss…

基于python高校选课系统设计与实现flask-django-nodejs-php

随着互联网技术的不断发展,高校选课系统的建设和应用已成为当前高校教育改革的重要方向。选课系统作为高校教务管理的重要组成部分,对于提高教学质量、提高学生的学习效率、优化教学资源配置具有重要的意义。本论文旨在探讨高校选课系统的设计与实现。随…

StringTable(字符串常量池)

目录 String的基本特性 String的内存分配 字符串拼接操作 intern()的使用 String的基本特性 String:字符串,使用一对""引起来表示 String声明为final的,不可被继承 String实现了Serializable接口:表示字符串是支持…

TIMESNET: TEMPORAL 2D-VARIATION MODELINGFOR GENERAL TIME SERIES ANALYSIS

本文整理至https://zhuanlan.zhihu.com/p/606575441这里是清华大学软件学院机器学习组官方公众号(THUML-LAB),欢迎关注我们,获取最新资讯! 本文介绍本组ICLR2023时间序列分析方向的最新工作:TimesNet: Temporal 2D-Variation Mode…

Python 实现1~100之间的偶数求和

result0 for i in range(101):if i%20:result result i print(result) 或者 result0 for i in range(2,101,2):result result i print(result)

从零开始学习如何使用 Postman 请求头

当你在使用 Postman 发送请求时,请求头(Headers)是你可以包含在 HTTP 请求中的重要部分之一。请求头包含了关于请求的元数据信息,这些信息对于服务器来处理请求是非常重要的。下面是一份详细的图文介绍,说明了如何在 P…

BUUCTF-Misc10

秘密文件1 1.打开附件 是一个流量包 2.Wireshark 用Wireshark打开 右键追踪tcp追踪流,发现一个以.rar结尾的压缩包 3.foremost 用foremost分离文件 发现有一个rar的文件夹 文件夹内有个加密的压缩包 4.ARCHPR 用ARCHPR工具对压缩包进行解密 5.得到flag [BJDCTF2…

【C语言进阶篇】自定义类型:结构体(上)

目录 1. 结构体类型的声明 ​编辑 1.1 结构体的创建和初始化 1.2 结构体的特殊声明 1.3 结构体的自引用 2. 结构体内存对齐 2.1 对齐规则 2.2 为什么存在内存对齐 2.3 修改默认对齐数 在我们描述简单对象的时候,使用已有的类型就足够了,比如: 但是当我们…

数据结构从入门到精通——排序的概念及运用

排序的概念及运用 前言一、排序的概念排序稳定性内部排序外部排序 二、排序运用三、常见的排序算法四、排序性能检测代码srand()clock() 五、oj排序测试代码 前言 排序是将数据按照一定规则重新排列的过程,常见规则有升序、降序等。排序算法如冒泡排序、快速排序等…

基于php健身房管理系统flask-django-python

根据现实需要,此系统我们设计出一下功能,主要有以下功能模板。 (1)前台功能:首页、运动器材、教练信息、营业信息、公告栏、在线留言、后台管理、个人中心。 (2)会员功能:首页、个人…

力扣700 二叉搜索树中的搜索 Java版本

文章目录 题目描述代码 题目描述 给定二叉搜索树(BST)的根节点 root 和一个整数值 val。 你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。 示例 1: 输入:root [4,2,7,…

C++_day6:2024/3/18

作业1:编程题: 以下是一个简单的比喻,将多态概念与生活中的实际情况相联系: 比喻:动物园的讲解员和动物表演 想象一下你去了一家动物园,看到了许多不同种类的动物,如狮子、大象、猴子等。现在…

一文读懂Partisia区块链的MOCCA 方案:让资产管理可信且可编程

在今年 1 月,Partisia Blockchain 在参加了达沃斯世界经济论坛时,宣布推出一种全新的链上资产管理方案 MOCCA (MPC On-Chain Custody Advanced),即多方计算链上托管高级解决方案。据悉该方案建立在 Partisia Blockchai…

腾讯云服务器多少钱一年?2024报价曝光,请查收

腾讯云服务器多少钱一年?61元一年起。2024年最新腾讯云服务器优惠价格表,腾讯云轻量2核2G3M服务器61元一年、2核2G4M服务器99元一年可买三年、2核4G5M服务器165元一年、3年756元、轻量4核8M12M服务器646元15个月、4核16G10M配置32元1个月、312元一年、8核…

海外代理IP在跨境电商中的五大应用场景

在我国跨境电商的发展中,海外代理IP的应用日益广泛,它不仅帮助商家成功打入国际市场,还为他们在多变的全球电商竞争中保持优势。下面是海外代理IP在跨境电商中五个关键的应用场景。 1、精准的市场分析 了解目标市场的消费者行为、产品趋势以…