class 继承的重点

简介: 前文已提过:在 class 出现之前,JavaScript 实现继承是件麻烦事,构造函数继承有加上原型上的函数不能复用的问题;原型链继承又存在引用值属性的修改不独立的问题;组合继承又存在两次调用构造函数的问题,寄生组合继承,写起来又太麻烦了,总之,在 class 出现前,JavaScipt 实现继承真是件麻烦事儿。

image.png

前文已提过:在 class 出现之前,JavaScript 实现继承是件麻烦事,构造函数继承有加上原型上的函数不能复用的问题;原型链继承又存在引用值属性的修改不独立的问题;组合继承又存在两次调用构造函数的问题,寄生组合继承,写起来又太麻烦了,总之,在 class 出现前,JavaScipt 实现继承真是件麻烦事儿。


然而,class 的出现真的改变这一现状了吗?


不如往下看。


写法



与函数类型相似,定义类也有两种主要方式:类声明和类表达式。

// 类声明 class Person {}

// 类表达式 const Animal = class {};


不过,与函数定义不同的是,虽然函数声明可以提升,但类定义不能。

与函数构造函数一样,多数编程风格都建议类名的首字母要大写,以区别于通过它创建的实例。


类可以包含:


  • 构造函数方法
  • 实例方法
  • 获取函数
  • 设置函数
  • 静态类方法


这些项都是可选的


constructor



class Person { 
    constructor(name) {
        this.name = name
        console.log('person ctor');
    }
}
let p1 = new Person("p1")


constructor 会告诉解释器 在使用 new 操作符创建类的新实例时,应该调用这个函数。

等同于


function Person(name){
    this.name = name
    console.log('person ctor')
}
let p1 = new Person("p1")


类构造函数与构造函数的主要区别是,这样写会报错:


class Animal {}
let a = Animal(); // TypeError: class constructor Animal cannot be invoked without 'new'


所以,new 操作符是强制要写的;


使用 new 时,原理与 new 一个对象也是一样的,因为太重要了,再强调一遍:

(1) 在内存中创建一个新对象。(2) 这个新对象内部的[[Prototype]]指针被赋值为构造函数的 prototype 属性。 (3) 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。 (4) 执行构造函数内部的代码(给新对象添加属性)。 (5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。


特性



从各方面来看,ECMAScript 类就是一种特殊函数。


我们可以用 typeof 打印试试:

class Person {} 
console.log(typeof Person); // function


也可以用 instanceof 检查它的原型链


class Person {} 
let p = new Person()
console.log(p instanceof Person); // true


通过 class 构造的每个实例都对应一个唯一的成员对象,这意味着所有成员都不会在原型上共享;


class Person {
 constructor() {
 this.name = new String('Jack');
 this.sayName = () => console.log(this.name);
 }
}
let p1 = new Person();
let p2 = new Person();
console.log(p1.name === p2.name) // false
console.log(p1.sayName === p2.sayName) // false


如果想要共享,就改写成方法,写在 constructor 外面:


class Person {
 constructor() {
 this.name = new String('Jack');
 }
 sayName(){
      console.log(this.name);
 }
}
let p1 = new Person();
let p2 = new Person();
console.log(p1.sayName === p2.sayName) // true


我们可以在方法前面加 static 关键字,实现:静态类成员。我们不能在类的实例上调用静态方法,只能通过类本身调用。不做赘述。


继承



ECMAScript 6 新增特性中最出色的一个就是原生支持了类继承机制。虽然类继承使用的是新语法,但背后依旧使用的是原型链


让我们再回顾构造函数继承和原型链继承 2 个经典的问题:


① 构造函数继承的问题:构造函数外在原型上定义方法,不能重用

function SuperType(){}
SuperType.prototype.sayName = ()=>{console.log("bob")}
function SubType(){
    SuperType.call(this) // 构造函数继承
}
let p1 = new SubType()
console.log(p1.sayName()) // Uncaught TypeError: p1.sayName is not a function


而原型链继承可以解决这一点:


function SuperType(){}
SuperType.prototype.sayName = ()=>{console.log("bob")}
function SubType(){}
SubType.prototype = new SuperType()  // 原型链继承
let p1 = new SubType()
console.log(p1.sayName()) // bob


② 原型链继承的问题:原型中包含的引用值会在所有实例间共享。


function SuperType(){
    this.name = ["bob","tom"];
}
function SubType(){}
SubType.prototype = new SuperType()  // 原型链继承
let p1 = new SubType()
p1.name.push("jerry")
let p2 = new SubType()
console.log(p2.name) //  ['bob', 'tom', 'jerry']


而构造函数继承可以解决这一点:


function SuperType(){
    this.name = ["bob","tom"];
}
function SubType(){
    SuperType.call(this) // 构造函数继承
}
let p1 = new SubType()
p1.name.push("jerry")
let p2 = new SubType()
console.log(p2.name) //  ['bob', 'tom']


class 继承有这两个问题吗??


代码一试便知:

class SuperType{}
SuperType.prototype.sayName = ()=>{console.log("bob")}
class SubType extends SuperType{
}
let p1 = new SubType()
p1.sayName() // bob


问题①,没有问题,在构造函数外写的原型继承,公共方法还是能访问的!!


class SuperType{
    constructor(){
        this.name=["bob","tom"]
    }
}
class SubType extends SuperType{
}
let p1 = new SubType()
let p2 = new SubType()
p1.name.push("Jerry")
console.log(p2.name) //  ['bob', 'tom']


问题②,没有问题,在 constructor 的引用值属性,修改不会产生干涉!!

class 继承完美的解决了构造函数继承的问题,和原型链继承的问题,写起来也没有组合继承、寄生继承那么麻烦,如果非得用 JS 模拟面向对象编程,class 必不可少!!


题外话



其实写 Class C 和 C.prototype 一起写是很危险的:明明都在操作面向对象的类了,还要操作原型链。类操作和原型操作是两种不同的设计思路,有兴趣可见本瓜一年前的一篇文章:“类”设计模式和“原型”设计模式——“复制”和“委托”的差异

出处。

相关文章
|
4月前
|
前端开发
WebView2 控件(基于 Microsoft Edge (Chromium) 的嵌入式浏览器控件),保存资源(图片、脚本、CSS)
WebView2 控件(基于 Microsoft Edge (Chromium) 的嵌入式浏览器控件),保存资源(图片、脚本、CSS)
281 38
|
4月前
|
人工智能 自然语言处理 搜索推荐
传统产品经理思维在AI时代‘失灵’,能力图谱如何助力AI产品经理构建认知框架?
本文AI产品专家三桥君探讨了AI产品经理在技术快速发展背景下如何通过构建AI能力图谱来指导智能产品设计。三桥君从知识与推理、自然语言处理、交互能力和辅助决策四个维度系统梳理AI核心能力,帮助产品经理理解技术边界与应用场景。能力图谱不仅是技术地图,更是方法论工具,能够有效指导从需求分析到产品落地的全流程,包括发现问题、设计闭环系统和规划产品路径。掌握这一框架将帮助AI产品经理突破技术认知局限,打造真正智能化的产品解决方案。
203 10
|
9月前
|
人工智能
RT-DETR改进策略【损失函数篇】| 2024 引进Focaler-IoU损失函数 加强边界框回归 (Focaler-DIoU、Focaler-GIoU、Focaler-CIoU)
RT-DETR改进策略【损失函数篇】| 2024 引进Focaler-IoU损失函数 加强边界框回归 (Focaler-DIoU、Focaler-GIoU、Focaler-CIoU)
274 1
|
10月前
|
存储 安全 数据管理
解锁企业数据管理统一身份认证难题,EasyMR助力企业敏捷提效
在数字经济时代,企业面临数据量爆炸式增长的挑战。据IDC预测,2025年全球数据总量将超175 ZB。大数据成为决策关键,但传统管理模式已难以应对。袋鼠云推出的EasyManager平台专注于大数据集群全生命周期管理,提供从集群创建到智能监控的一站式解决方案,帮助企业高效处理海量数据,降低运营成本,提升业务敏捷性。同时,结合Kerberos、OpenLDAP和SSSD,EasyManager实现了统一身份认证与管理,确保企业在数据洪流中保持竞争力与安全性。
|
边缘计算 物联网 开发者
2024年提升开发效率的十大技巧
2024年,软件开发领域持续快速发展,新技术和工具层出不穷。本文总结了十大提升开发效率的技巧,包括精通Git Hooks自动化流程、利用Docker容器化技术、拥抱无代码/低代码平台、集成AI/ML、关注IoT、重视网络安全、采用云原生开发和微服务架构、探索边缘计算、利用AR和即时应用技术,以及参与开源软件项目。这些技巧旨在帮助开发者适应技术变革,提高工作效率。
|
机器学习/深度学习 人工智能 Java
探索软件测试中的自动化框架选择与优化策略####
本文深入探讨了在软件测试领域,面对众多自动化测试框架时,如何根据项目特性、团队技能及长远规划做出最佳选择,并进一步阐述了优化这些框架以提升测试效率与质量的策略。通过对比分析主流自动化测试框架的优劣,结合具体案例,本文旨在为测试团队提供一套实用的框架选型与优化指南。 ####
|
设计模式 Java Spring
Spring Boot监听器的底层实现原理
Spring Boot监听器的底层实现原理主要基于观察者模式(也称为发布-订阅模式),这是设计模式中用于实现对象之间一对多依赖的一种常见方式。在Spring Boot中,监听器的实现依赖于Spring框架提供的事件监听机制。
337 1
|
算法 C++
【算法解题思想】动态规划+深度优先搜索(C/C++)
【算法解题思想】动态规划+深度优先搜索(C/C++)
|
搜索推荐
百度百科都是谁写的
百度百科是全民共建的网络百科全书,允许注册用户编辑词条,强调平等、协作与分享。它拥有严格的审核机制,确保内容客观、权威,以参考资料为支撑。编辑者来自各行各业,从学生到专业人士,他们的贡献提升了百科的可信度。新创建的词条若具丰富引用,尤其来自政府网站,其可信度更高。通过用户间的交流与合作,百度百科不断进化和完善。
806 1
|
关系型数据库 MySQL 应用服务中间件
基于Ubuntu搭建LNMP环境
本教程介绍如何在Ubuntu 18.04操作系统的ECS实例上搭建一套Nginx、MySQL和PHP应用的开发环境。