ES6提供了ES5这个构造函数的语法糖class用来模拟类
ES5中模拟类的流程
- 在ES6中提供了
class
这个 构造函数的语法糖 来模拟类,但它并不是真正的类,实际上走的还是ES5中的模拟类的流程,只是看起来更像类了。
ES5中模拟类的流程:
- 构造函数和正常创建函数方法一样
- 使用new关键字调用函数即相当于调用构造函数
1 | // ES5中模拟类 |
使用new调用函数生成对象的流程
当用new关键字调用函数时,发生了什么?为什么会获得个新的对象?
- 创建一个空的对象
- 以构造函数的prototype属性作为空对象的原型
- this指向为这个空对象
- 执行构造函数
- 如果构造函数没有返回值则返回创建的空对象
使用原理模拟一个构造函数
根据上面“使用new调用函数生成对象的流程”的原理,我们自己创建一个构造函数Constructor()
,该函数的参数1接受一个函数作为构造函数,参数2接受数组
1 | // 模拟构造函数 |
- 帮助理解:
- fn.prototype: 构造函数 fn 的 prototype 属性(一个对象)
- Object.create: 以一个现有对象作为原型,创建一个新对象 _this
- 新对象读属性/方法读时会顺着原型链找,对象的隐式原型、构造函数的显式原型(即 Object.create() 方法的第一个参数)
- 补充:Object.create() 静态方法以一个现有对象作为原型,创建一个新对象
- 第一个参数被视为新创建的对象的构造函数的显式原型
ES6中class的继承
- 继承(extends)可以让子类获得父类的方法、属性
- 可以扩充增加新的方法属性等
- 父类(基类)-被继承的类
- 子类-继承后的类
- 子类extends父类后,需要在构造函数中使用
super();
(相当于执行父类的构造函数)
简单例子:
给子类也定义父类的属性值:
1 | class Human { |
单纯给子类定义属性值:
1 | class FEEngineer extends Human { |
实际案例:网游职业系统
1 | // 网游职业系统 |
super 关键字
注意:super 会执行父类的构造函数,在执行过程中,父类构造函数中的 this 指向正在被创建的子类的实例(MDN参考)
- super作为父类构造函数调用
- 比如上面的例子,也就是将子类的this丢到父类的构造函数中跑一遍
- super作为对象的方式调用
- 非静态方法中访问super->super指向 父类原型
- 在静态方法中访问super->super指向 父类
非静态方法中访问super->super指向 父类原型:
1 | class Human { |
在静态方法中访问super->super指向 父类:
1 | class Human { |
静态方法与静态属性 都是类自身的,通过类创建的对象不会拥有 :
1 | Human.total = 8999999; // 给Human类添加静态属性 total |
简单的多态
- ES6中没有提供接口来实现 多态
- 多态:同一个接口在不同情况下做不一样的事情,即 相同的接口不同的表现
- 接口:接口本身只是一组定义,实现都是在类里面。
- 也就是说我们可以通过方法的重写实现多态。
实现多态简单例子
只需要在子类中定义和父类同名的方法覆盖掉父类方法就可以实现多态:
1 | class Human { |
子类依然可以通过super访问父类同名方法:
1 | class Human { |
对比 重载
- 重载 不是发生在父子中间的,他是根据函数的参数类型、个数 让函数做不一样的事。而多态是方法的重写。
- 区分 多态、重载:
- 重载 通常是指在同一个函数名下,根据传递的参数的不同数量或类型,执行不同的操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32class SimpleCalc {
addCalc(...args) {
if (args.length === 0) {
return this.zero();
}
if (args.length === 1) {
return this.onlyOneArgument(args);
}
return this.add(args);
}
zero() {
return 0;
}
onlyOneArgument() {
return args[0];
}
add(args) {
return args.reduce((a, b) => a + b, 0);
}
}
function post(url, header, params) {
// 其实不算准确的例子,只是是那么个意思
// 函数重载通常是在不同的参数组合下执行不同的逻辑
if (!params) {
params = header;
header = null; // undefined
}
}
post('https://imooc.com', {
a: 1,
b: 2
}) - 多态 是方法的 重写,两者是没有关系的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38const ModelMap = {
'红眼僵尸': 1,
'南瓜精': 2,
'独眼蝠': 3,
'绿眼僵尸': 4
}
// 怪物
class Monster {
constructor(name, level, model) {
this.model = model;
this.name = name;
this.level = level;
}
attack() {
throw Error('须由子类来实现 `attack`(攻击)方法');
}
}
class RedEyeZombie extends Monster {
constructor() {
super('红眼僵尸', 10, ModelMap['红眼僵尸']);
}
}
class GreenEyeZombie extends Monster {
constructor() {
super('绿眼僵尸', 10, ModelMap['绿眼僵尸']);
}
attack() {
console.log('绿眼僵尸发动了攻击');
}
}
const gez = new GreenEyeZombie();
gez.attack();
const rez = new RedEyeZombie();
rez.attack();
/*
绿眼僵尸发动了攻击
Uncaught Error: 须由子类来实现 `attack`(攻击)方法
*/
- 重载 通常是指在同一个函数名下,根据传递的参数的不同数量或类型,执行不同的操作
ES5继承的实现
方法有很多,这里只说其中一种。
方法:利用构造函数,但此方法不能继承父类原型链上的方法:
1 | // 父类是P 子类是C |
让子类的原型=父类的实例,即可解决问题:
1 | // 父类是P 子类是C |
babel
- babel是一个JS编译器,他可以将大部分浏览器还不支持的特性编译成浏览器可良好支持的JS代码。
- 可以在babel中文官网中看到编译效果:
安装babel
- 需要先安装node
- 然后可以在babel中文官网=>设置=>CLI中看到安装方法
- 虽然可以全局安装,但建议对单个项目进行本地安装,因为:
- 同一机器上的不同的项目可以依赖不同版本的Babel,这允许你一次更新一个项目。
- 这意味着在你的工作环境中没有隐含的依赖项。它将使你的项目更方便移植、更易于安装。
本地安装Babel CLI的命令:
1 | npm install --save-dev @babel/core @babel/cli |
注意:安装之前项目中需要一个package.json(可以通过npm init-y
来生成package.json),这可以保证npx命令产生合适的交互,安装成功后package.json中应包括
1 | { |
其中,**devDependencies
的意思是生产时依赖的包,安装时使用的是--save-dev
这些包上线时就不会使用了,因为上线的是编译后的结果。
而相对的就有dependencies
,安装时使用的是--save
,它包含的包就是上线运行时依赖的包**。
配置babel运行命令
编译后显示在终端
在package.json的scripts中,添加:
1 | "build": "babel entry.js" |
则在entry.js
中书写ES6代码后,在终端输入npm run build
即可执行babel,在终端可查看编译后的代码(但此时ES6代码还是毫无变化,这是因为我们没有设置“转换规则”)。
编译后输出到别的文件中
1 | "build": "babel entry.js -o index.js" |
也就是执行npm run build
则将entry.js
进行babel编译后输出到index.js
文件中。
那么我们就能项目中就会生成index.js
文件放置变异后的js代码。
但此时我们每次修改entry.js
代码后都需要手动执行npm run build
来编译。
运行以后一直自动编译
1 | "build": "babel entry.js -o index.js -w" |
加上-w
后则只需执行一次npm run build
,接下来每次entry.js
修改报错都会自动编译。
安装 转换规则babel-preset-env
1.安装 转换规则:
1 | npm install @babel/preset-env --save-dev |
2.devDependencies
多了以下代码说明安装成功:
1 | "babel-preset-env": "^1.7.0" |
创建配置文件(.babelrc
)
- **配置文件(
.babelrc
)**:用来告诉babel根据什么规则来编译代码的。 - 当运行babel命令时,就会去找
.babelrc
文件,根据该文件内规定的配置去编译代码。 - 注意:
.babelrc
文件中使用的是json格式,属性名必须加引号
在目录下新建.babelrc
文件:
1 | { |
上面试最简单的配置,详细配置可参考官网。
例子
entry.js:
1 | const add = (a, b) => (a + b); |
运行npm run build
后,index.js:
1 | ; |
babel插件
- npmjs和babel官网都提供了很多babel插件,可以在上面进行查询。
- 通过插件可以实现一些ES6不支持的编译,比如ES6中不支持使用static生成静态属性,但我们可以通过**@babel/plugin-proposal-class-properties插件**使之成立。
例子
安装@babel/plugin-proposal-class-properties插件:
1 | yarn add @babel/plugin-proposal-class-properties --dev |
配置:
在配置文件(.babelrc
)中添加:
1 | "plugins": ["@babel/plugin-proposal-class-properties"] |
正常运行不报错了:
将entry.js放到index.html上后可在index.html上看到效果。
额外知识点
- 遇到大量相同代码时,抽出作为一个方法,在不同地方调用该方法即可。如部分数据不同,可采用参数的方法进行传递:
- 实现链式操作:想要实现最后的链式操作,就需要每一次操作都返回操作的对象: