JS高级程序设计之class继承重点详解


Posted in Javascript onJuly 07, 2022

引言

前文已提过:在 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 一起写是很危险的:明明都在操作面向对象的类了,还要操作原型链。类操作和原型操作是两种不同的设计思路,有兴趣可见本瓜一年前的一篇文章:“类”设计模式和“原型”设计模式——“复制”和“委托”的差异

以上就是JS高级程序设计之class继承重点详解的详细内容,更多关于JS高级程序设计class继承的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
基于jQuery的倒计时实现代码
May 30 Javascript
jquery选择器之基本过滤选择器详解
Jan 27 Javascript
json格式数据的添加,删除及排序方法
Jan 21 Javascript
vue中用H5实现文件上传的方法实例代码
May 27 Javascript
详解基于Angular4+ server render(服务端渲染)开发教程
Aug 28 Javascript
springMvc 前端用json的方式向后台传递对象数组方法
Aug 07 Javascript
Easyui 关闭jquery-easui tab标签页前触发事件的解决方法
Apr 28 jQuery
微信小程序事件对象中e.target和e.currentTarget的区别详解
May 08 Javascript
js实现一个简易计算器
Mar 30 Javascript
详解将微信小程序接口Promise化并使用async函数
Aug 05 Javascript
js实现列表向上无限滚动
Jan 13 Javascript
es6中使用map简化复杂条件判断操作实例详解
Feb 19 Javascript
JS class语法糖的深入剖析
Jul 07 #Javascript
MutationObserver在页面水印实现起到的作用详解
Jul 07 #Javascript
js作用域及作用域链工作引擎
Promise静态四兄弟实现示例详解
Jul 07 #Javascript
Three.js实现雪糕地球的使用示例详解
二维码条形码生成的JavaScript脚本库
Jul 07 #Javascript
JS实现简单的九宫格抽奖
You might like
Zend的AutoLoad机制介绍
2012/09/27 PHP
php获取服务器信息的实现代码
2013/02/04 PHP
ThinkPHP 404页面的设置方法
2015/01/14 PHP
php制作文本式留言板
2015/03/18 PHP
php实现中文字符截取防乱码方法汇总
2015/04/29 PHP
PHP读取PPT文件的方法
2015/12/10 PHP
Symfony2学习笔记之控制器用法详解
2016/03/17 PHP
PHP+Ajax实现的无刷新分页功能详解【附demo源码下载】
2017/07/03 PHP
PHP中abstract(抽象)、final(最终)和static(静态)原理与用法详解
2020/06/05 PHP
控制打印时页眉角的代码
2007/02/08 Javascript
javascript中最常用的继承模式 组合继承
2010/08/12 Javascript
Jquery 自定义动画概述及示例
2013/03/29 Javascript
jquery控制左右箭头滚动图片列表的实例
2013/05/20 Javascript
jQuery 借助插件Lavalamp实现导航条动态美化效果
2013/09/27 Javascript
JavaScript获取当前日期是星期几的方法
2015/04/06 Javascript
使用JavaScript刷新网页的方法
2015/06/04 Javascript
原生js配合cookie制作保存路径的拖拽
2015/12/29 Javascript
jQuery实现的跨容器无缝拖动效果代码
2016/06/21 Javascript
Vue结合原生js实现自定义组件自动生成示例
2017/01/21 Javascript
jquery dataTable 后台加载数据并分页实例代码
2017/06/07 jQuery
AngularJS的ng-click传参的方法
2017/06/19 Javascript
关于页面刷新vuex数据消失问题解决方案
2017/07/03 Javascript
Vue组件通信实践记录(推荐)
2017/08/15 Javascript
javascript实现最长公共子序列实例代码
2018/02/05 Javascript
vue 对象添加或删除成员时无法实时更新的解决方法
2019/05/01 Javascript
Vue中el-form标签中的自定义el-select下拉框标签功能
2020/04/20 Javascript
[08:40]Navi Vs Newbee
2018/06/07 DOTA
PyQt5每天必学之组合框
2018/04/20 Python
python读取LMDB中图像的方法
2018/07/02 Python
python3爬虫中异步协程的用法
2020/07/10 Python
html特殊符号示例 html特殊字符编码对照表
2014/01/14 HTML / CSS
Haglöfs瑞典官方网站:haglofs火柴棍,欧洲顶级户外品牌
2018/10/18 全球购物
英国在线药房和在线药剂师:Chemist 4 U
2020/01/05 全球购物
电脑教师的教学自我评价
2013/11/26 职场文书
四风对照检查剖析材料
2014/10/07 职场文书
2016毕业实习单位评语大全
2015/12/01 职场文书