编码遗传学:JavaScript 继承之道

2023-12-14 11:44

本文主要是介绍编码遗传学:JavaScript 继承之道,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

引入

JavaScript中的继承问题是一个很重要的知识点,很多面试都会问到。本文主要来详细地讲解JavaScript实现继承的不同方法。其中包括原型链继承、构造函数基础、组合式继承以及寄生组合式继承等ES5的实现方式,也会介绍ES6新出现的class继承

原型链继承

原型链继承简单地将子类的原型对象指向父类实例,这样子类实例在无法找到对应的属性或方法时会继续向其原型对象,即父类实例上查找,从而实现对父类属性和方法的继承。

function Person() {this.name = 'N-A';
}
Person.prototype.getName = function() {return this.name;
}
function Student() {}
Student.prototype = new Person();
// 原型的实例等于自身
Student.prototype.constructor = Student;
const student = new Student();
console.log(student.name); // N-A
console.log(student.getName()); // N-A

但这种方法存在一些缺陷:

1.引用类型共享:由于所有子类实例的原型都指向同一个父类实例,当子类实例修改继承的引用类型属性时,会影响所有子类实例,因为它们共享相同的原型对象。

function Person() {this.obj = {name: 'N-A',age: 5};
}
function Student() {}
Student.prototype = new Person();
// 原型的实例等于自身
Student.prototype.constructor = Student;
const student1 = new Student();
student1.obj.name = 'CSDN';
const student2 = new Student();
console.log(student2.obj.name); // CSDN

2.无法传参:在创建子类实例时无法向父类构造函数传递参数,因此无法像使用 super() 一样实现在子类中调用父类构造函数的功能,限制了灵活性和定制性。

构造函数继承

构造函数继承通过在子类构造函数中调用父类构造函数并使用子类的 this,将父类的成员属性和方法直接挂载到子类实例上。这种方式避免了子类实例共享一个原型实例,同时也能够向父类构造函数传递参数。

function Person(name) {this.name = name
}
Person.prototype.getName = function() {return this.name;
}
function Student() {Person.apply(this, arguments);
}const student = new Student('N-A');
console.log(student.name); // N-A

然而,这种继承方式同样也存在缺陷:无法继承父类原型上的属性和方法,子类只能继承到父类构造函数中定义的属性和方法,而无法直接访问和继承父类原型上的属性和方法。这导致子类无法复用父类原型链上的方法,降低了代码的复用性和灵活性。

虽然构造函数继承解决了一些原型链继承的问题,但它的局限性在于无法继承父类原型上的属性和方法,限制了子类的功能扩展和代码复用。

组合式继承

组合继承结合了原型链继承和构造函数继承的优点,通过在子类构造函数中调用父类构造函数来实现对父类属性的继承,同时利用原型链继承来继承父类原型上的方法和属性

function Person(name) {this.name = name;
}
Person.prototype.getName = function() {return this.name;
}
function Student() {// 构造函数继承Person.apply(this, arguments)
}
// 原型式继承
Student.prototype = new Person();// 原型的实例等于自身
Student.prototype.constructor = Student;const student = new Student('N-A');
console.log(student.name); // N-A
console.log(student.getName()); // N-A

但是,这种继承方式也存在一些缺陷:重复调用构造函数:每次创建子类实例时,都会执行两次构造函数。一次是通过 Person.apply 将父类的属性赋予子类实例,另一次是使用 new Person() 创建父类实例,并将其作为子类原型的一部分。这不影响对父类属性的继承,但会导致子类原型中存在两份相同的属性和方法,造成资源浪费和内存占用。

尽管组合继承解决了原型链继承和构造函数继承各自的缺陷,但重复调用构造函数会导致资源浪费,使得子类原型上存在冗余的属性和方法,不够优雅。

寄生式组合继承

寄生式组合继承是一种通过在子类构造函数中使用 Object.create 方法来优化组合继承的方式,解决了重复调用构造函数的问题。

function Person(name) {this.name = name;
}
Person.prototype.getName = function() {return this.name;
}
function Student() {// 构造函数继承Person.apply(this, arguments)
}
// 原型式继承
// Student.prototype = new Person();
Student.prototype = Object.create(Person.prototype);// 原型的实例等于自身
Student.prototype.constructor = Student;const student = new Student('N-A');
console.log(student.name); // N-A
console.log(student.getName()); // N-A

它的基本原理是:

1. 使用 Object.create 创建一个临时的中间对象,这个对象的原型指向了父类的原型对象,但不直接执行父类的构造函数。

2. 将这个临时创建的对象赋值给子类的原型,这样子类的原型就可以继承自父类的原型,但避免了多余的构造函数调用。

这种继承方式在 ES5 中被认为是相对成熟且高效的继承方式,解决了组合继承中构造函数被执行两次的问题,保留了原型链继承和构造函数继承的优点,同时避免了它们的缺点。

Class继承

ES6 引入了类的概念,可以使用 classextends 关键字来实现类的继承。

class Person {constructor(name) {this.name = name;}greet() {return `Hello, my name is ${this.name}.`;}
}class Student extends Person {constructor(name, level) {super(name); // 调用父类构造函数来初始化父类的属性this.level = level;}study() {return `${this.name} studies at level ${this.level}.`;}
}// 创建一个 Student 实例
const student = new Student('N-A', 'Senior');console.log(student.greet()); // 输出:Hello, my name is N-A.
console.log(student.study()); // 输出:N-A studies at level Senior.

在这个示例中:

Person 类有一个 name 属性和一个 greet 方法。Student 类通过 extends 关键字继承了 Person 类,并在构造函数中使用 super() 调用父类的构造函数初始化父类的属性。

Student 类还有一个 study 方法。创建 Student 类的实例,并调用继承自父类和自身定义的方法。

类继承使得 JavaScript 中的继承更加直观和易用,同时也更符合传统面向对象编程的习惯。通过 extends 关键字,子类可以轻松地继承父类的属性和方法,并且可以在子类中添加自己的属性和方法。使用 super() 可以方便地调用父类构造函数,初始化父类的属性。

好啦,本文就到这里啦!

这篇关于编码遗传学:JavaScript 继承之道的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/492393

相关文章

SpringBoot中四种AOP实战应用场景及代码实现

《SpringBoot中四种AOP实战应用场景及代码实现》面向切面编程(AOP)是Spring框架的核心功能之一,它通过预编译和运行期动态代理实现程序功能的统一维护,在SpringBoot应用中,AO... 目录引言场景一:日志记录与性能监控业务需求实现方案使用示例扩展:MDC实现请求跟踪场景二:权限控制与

Java NoClassDefFoundError运行时错误分析解决

《JavaNoClassDefFoundError运行时错误分析解决》在Java开发中,NoClassDefFoundError是一种常见的运行时错误,它通常表明Java虚拟机在尝试加载一个类时未能... 目录前言一、问题分析二、报错原因三、解决思路检查类路径配置检查依赖库检查类文件调试类加载器问题四、常见

Java注解之超越Javadoc的元数据利器详解

《Java注解之超越Javadoc的元数据利器详解》本文将深入探讨Java注解的定义、类型、内置注解、自定义注解、保留策略、实际应用场景及最佳实践,无论是初学者还是资深开发者,都能通过本文了解如何利用... 目录什么是注解?注解的类型内置注编程解自定义注解注解的保留策略实际用例最佳实践总结在 Java 编程

解决IDEA报错:编码GBK的不可映射字符问题

《解决IDEA报错:编码GBK的不可映射字符问题》:本文主要介绍解决IDEA报错:编码GBK的不可映射字符问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录IDEA报错:编码GBK的不可映射字符终端软件问题描述原因分析解决方案方法1:将命令改为方法2:右下jav

Java 实用工具类Spring 的 AnnotationUtils详解

《Java实用工具类Spring的AnnotationUtils详解》Spring框架提供了一个强大的注解工具类org.springframework.core.annotation.Annot... 目录前言一、AnnotationUtils 的常用方法二、常见应用场景三、与 JDK 原生注解 API 的

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

Java中的StringBuilder之如何高效构建字符串

《Java中的StringBuilder之如何高效构建字符串》本文将深入浅出地介绍StringBuilder的使用方法、性能优势以及相关字符串处理技术,结合代码示例帮助读者更好地理解和应用,希望对大家... 目录关键点什么是 StringBuilder?为什么需要 StringBuilder?如何使用 St

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推