编码遗传学: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

相关文章

java实现延迟/超时/定时问题

《java实现延迟/超时/定时问题》:本文主要介绍java实现延迟/超时/定时问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java实现延迟/超时/定时java 每间隔5秒执行一次,一共执行5次然后结束scheduleAtFixedRate 和 schedu

Java Optional避免空指针异常的实现

《JavaOptional避免空指针异常的实现》空指针异常一直是困扰开发者的常见问题之一,本文主要介绍了JavaOptional避免空指针异常的实现,帮助开发者编写更健壮、可读性更高的代码,减少因... 目录一、Optional 概述二、Optional 的创建三、Optional 的常用方法四、Optio

Spring Boot项目中结合MyBatis实现MySQL的自动主从切换功能

《SpringBoot项目中结合MyBatis实现MySQL的自动主从切换功能》:本文主要介绍SpringBoot项目中结合MyBatis实现MySQL的自动主从切换功能,本文分步骤给大家介绍的... 目录原理解析1. mysql主从复制(Master-Slave Replication)2. 读写分离3.

idea maven编译报错Java heap space的解决方法

《ideamaven编译报错Javaheapspace的解决方法》这篇文章主要为大家详细介绍了ideamaven编译报错Javaheapspace的相关解决方法,文中的示例代码讲解详细,感兴趣的... 目录1.增加 Maven 编译的堆内存2. 增加 IntelliJ IDEA 的堆内存3. 优化 Mave

Java String字符串的常用使用方法

《JavaString字符串的常用使用方法》String是JDK提供的一个类,是引用类型,并不是基本的数据类型,String用于字符串操作,在之前学习c语言的时候,对于一些字符串,会初始化字符数组表... 目录一、什么是String二、如何定义一个String1. 用双引号定义2. 通过构造函数定义三、St

springboot filter实现请求响应全链路拦截

《springbootfilter实现请求响应全链路拦截》这篇文章主要为大家详细介绍了SpringBoot如何结合Filter同时拦截请求和响应,从而实现​​日志采集自动化,感兴趣的小伙伴可以跟随小... 目录一、为什么你需要这个过滤器?​​​二、核心实现:一个Filter搞定双向数据流​​​​三、完整代码

SpringBoot利用@Validated注解优雅实现参数校验

《SpringBoot利用@Validated注解优雅实现参数校验》在开发Web应用时,用户输入的合法性校验是保障系统稳定性的基础,​SpringBoot的@Validated注解提供了一种更优雅的解... 目录​一、为什么需要参数校验二、Validated 的核心用法​1. 基础校验2. php分组校验3

Java Predicate接口定义详解

《JavaPredicate接口定义详解》Predicate是Java中的一个函数式接口,它代表一个判断逻辑,接收一个输入参数,返回一个布尔值,:本文主要介绍JavaPredicate接口的定义... 目录Java Predicate接口Java lamda表达式 Predicate<T>、BiFuncti

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

Spring Security方法级安全控制@PreAuthorize注解的灵活运用小结

《SpringSecurity方法级安全控制@PreAuthorize注解的灵活运用小结》本文将带着大家讲解@PreAuthorize注解的核心原理、SpEL表达式机制,并通过的示例代码演示如... 目录1. 前言2. @PreAuthorize 注解简介3. @PreAuthorize 核心原理解析拦截与