这阵子在整理JS的7种继承方式,发现很多文章跟视频,讲解后都不能让自己理解清晰,索性自己记录一下,希望个位发表需要修改的意见,共勉之。
部分文章总结出来的数据可能是错误的,网络上太多数据了,有些不严谨,当然我也可能是其中一人,如果有问题,欢迎提出来,共同进步。
JS的7种并不是逐级进化的(个人觉得~),可以参考下图:
JS一开始的初衷就是为了方便开发者,当初的开发也比较简单就是单纯的页面,随着浏览器不断地发展,现在功能越来越多了,所以,才有7种继承的进化史。
class 是es6新增的特性,避免了开发者去使用繁杂的原型链继承,构造函数继承,组合式继承,原型式继承,寄生式继承,寄生组合式继承。
比如本人,一上来就用class…无奈面试官可不理会你这些哈哈:)
mdn参考文献:此处 文献中的概念跟大学学习C++的类,有点不一样~~~
JS一开始设计就是开发一些简单的页面,本文尽量按照面向对象的类来讲解,因为不知道哪天ES又升级了呢?
由于篇幅过长,就拿几个重点来讲解
let person = new Parent(); // error~ 未声明 ReferenceError
class Parent(){}
...
class Parent(){} // error~ 重复定义
一个类的类体是一对花括号/大括号 {} 中的部分。这是你定义类成员的位置,如方法或构造函数。
这里大概了解一下,类的类体中有什么内容,下文再继续讲解:
class Rectangle {
// constructor
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea()
}
// Method
calcArea() {
return this.height * this.width;
}
// Static 下面有
}
const square = new Rectangle(10, 10);
console.log(square.area);
// 100
类的静态方法,存放在类中,而不是实例后的对象
static 关键字用来定义一个类的一个静态方法。调用静态方法不需要实例化 (en-US) 该类,但不能通过一个类实例调用静态方法。静态方法通常用于为一个应用程序创建工具函数。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static displayName = "Point";
// 用来计算Point实例化后的2个对象之间的距离
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.hypot(dx, dy);
}
}
const p1 = new Point(5, 5);
const p2 = new Point(10,10);
p1.displayName;
// undefined
p1.distance;
// undefined
console.log(Point.displayName);
// "Point"
console.log(Point.distance(p1, p2));
// 7.0710678118654755
class Parent {
constructor(name) {
console.warn("Parent 实例化了...");
this._name = name;
}
sayName() {
console.log("我的名字:", this._name);
}
get name() {
return this._name;
}
set name(name) {
this._name = name;
}
}
let p = new Parent("钟先生", 31);
console.log("原先的名字:", p.name);
p.name = "钟先生超级帅";
console.log("通过class set 修改后的名字:", p.name);
关于类这个概念本文章就不过多描述,一个是篇幅有限,一个是本人也没研究透彻,类是前人提出的概念,划分出3种继承:
在JS说人话就是可以通过对象
.操作符进行操作。
JS应用暂时不知道。。。
在JS说人话就是不能通过.操作符直接修改,只能通过实例对应的类方法跟get/set存取器进行修改
。小插曲:
JavaScript有一个非强制的但很有用的编程惯例
,就是对所以私有变量或函数名加一个下划线(_
)作为前缀,以标识他们是私有的,仅用于提醒开发人员。
该操作是非强制性的,对于一些实例对象的属性名还是可以通过
.
操作符修改。
这里只是标识作用,用于提醒开发者,并不能限制开发者直接修改其属性。
但是class继承可以划分出,公有属性以及私有属性。
super 关键字用于调用对象的父对象上的函数。
class Cat {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
class Lion extends Cat {
speak() {
super.speak();
console.log(this.name + ' roars.');
}
}
// 打印2条数据
// 比如 threejs 中就有过这段代码
抽象子类或者 mix-ins 是类的模板。一个 ECMAScript 类只能有一个单超类,所以想要从工具类来多重继承的行为是不可能的。子类继承的只能是父类提供的功能性。因此,例如,从工具类的多重继承是不可能的。该功能必须由超类提供。
一个以超类作为输入的函数和一个继承该超类的子类作为输出可以用于在 ECMAScript 中实现混合:
有点类似寄生模式,通过工厂模式,扩展出你的 target 类。
var calculatorMixin = (Base) =>
class extends Base {
calc() {}
};
var randomizerMixin = (Base) =>
class extends Base {
randomize() {}
};
使用 mix-ins 的类可以像下面这样写:
class Foo {}
class Bar extends calculatorMixin(randomizerMixin(Foo)) {}
类的继承,主要通过关键字extends
这边弄的稍微复杂了,但是没办法,东西很多,很多情况要对比,还没有跟寄生组合式继承对比呢~~~
父类属性:
基础数据类型
,这里加上_
,指示是私有变量,告诉开发人员不要随意修改,但是还是可以修改的…子类属性:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue路由中history模式的实现</title>
</head>
<body>
<h1>Vue路由中history模式的实现</h1>
<button id="myBtn">改变url</button>
<script>
class Parent {
constructor(name) {
// 公共属性,可以通过 .操作符修改
this._name = name;
// 放在了实例上
this.sayName1 = function () {
console.log(`我的名字1是:${this._name}`);
};
}
// 公共方法-放在原型链上
sayName() {
console.log(`我的名字是:${this._name}`);
}
get name() {
return this._name;
}
// 当然类中内部修改也是可以的
set name(name) {
this._name = name;
}
// 静态方法,一般用于比较2个实例,这里比较谁的名字长
static compareName(a, b) {
console.log(`${a.name}, 名字长度为${a.name.length}`);
console.log(`${b.name}, 名字长度为${b.name.length}`);
console.log(
(a.name.length > b.name.length ? a.name : b.name) + " 的名字长"
);
return a.name.length > b.name.length;
}
}
console.log("=========父类=========");
let p = new Parent("Penk");
p.sayName();
p.name = "Penk是个码农";
p.sayName();
class Children extends Parent {
// 私有变量
// 1. 构造函数
// 2. 内部方法或者get、set读取器
#job = "没工作";
// 构造函数
constructor(name, job) {
super(name);
this.#job = job;
}
// 内部方法
sayJob() {
console.log(`我叫${this.name},我的工作是:${this.#job}`);
}
// 读取器
get job() {
return this.#job;
}
set job(j) {
this.#job = j;
}
}
console.log("=========子类=========");
let person = new Children("钟先生", "程序员");
person.sayName();
person.name = "钟先生超级帅";
person.sayName();
// 直接修改,因为是公共属性
person._name = "钟先生";
person.sayName();
person.sayJob();
person.job = "扫地大叔1111111";
person.sayJob();
// 无效,根本读不到该值...
person["#job"] = "程序员";
person.sayJob();
// 静态方法比较长度
let person2 = new Children("刘备", "主公");
Parent.compareName(person, person2);
console.log(p);
console.log(person);
</script>
</body>
</html>
父类实例化对象,通过class 存取器修改_name属性
console.log("=========父类=========");
let p = new Parent("Penk");
p.sayName();
p.name = "Penk是个码农";
p.sayName();
子类,实例化对象,通过存取器修改,也通过了对象属性直接修改。
console.log("=========子类=========");
let person = new Children("钟先生", "程序员");
person.sayName();
person.name = "钟先生超级帅";
person.sayName();
// 直接修改,因为是公共属性
person._name = "钟先生";
person.sayName();
这边重复打印数据,是因为子类重写的sayName方法中,通过super 调用了父类的sayName
打印了子类的sayJob方法,因为#job是私有属性,只能通过读取器job进行修改:
person.sayJob();
person.job = "扫地大叔1111111";
person.sayJob();
// 无效,根本读不到该值...
person["#job"] = "程序员";
person.sayJob();
调用了类中的静态方法,静态方法一般不是用于对对象的操作,这边的功能是比较2个名字的长短,虽然有点不实用…
// 静态方法比较长度
let person2 = new Children("刘备", "主公");
Parent.compareName(person, person2);
class 不仅仅是简化了之前讲解的6种继承方式,也更加严谨,提供了更多的更像面向对象变成的方法(读取器,静态方法等)。但是不同的项目需要进行技术调研,class或许不是最优的解决方案,但一定是最优雅的。