在ES6之前,创建类使用的是构造函数和原型链,这种编写方式和编写普通的函数过于相似,因此代码并不容易理解和区分
在ES6(ECMAScript2015)新的标准中使用了class关键字来直接定义类
但是类本质上依然是前面所讲的构造函数、原型链的语法糖而已
只不过相比ES5中使用构造函数来定义类,在ES6中使用class来定义类的时候,对原本的定义方式又做了一定程度的增强
创建方式
// 类声明
// 类的首字母一般大写
class Person {}
// 类表达式
const Student = class {}
基本使用
// class中的大括号是一种新的用于定义类的方法
// 所以在类中的方法和方法之间不需要使用逗号进行分割
class Person {
// 在使用new关键字调用Person类的时候
// 默认会调用类的构造方法进行属性的初始化操作
// 如果没有显示定义类的构造方法的时候,默认会存在一个空的构造方法
constructor() {} // -> 这是默认的构造方法
// funName() {} 这种定义方式是类中固定语法
// 并不是funName: function() {} 这种形式的语法糖
// 在ES6中 类的构造函数最多只能存在一个,不可重复定义
}
// class定义类将类的属性和方法封装在了一个大括号中
// 相比使用构造函数定义类,提高了内聚度,降低了耦合度
class Person {
// 类中的构造函数的运行方式和ES5中构造函数的运行方式是一致的
constructor(name, age) {
this.name = name
this.age = age
}
// 在类中定义的方法默认会被放置到类的原型对象上
running() {
console.log('running')
}
// 在类中也可以使用如下方式定义方法和属性
// 这种定义类的属性和方法的定义方式被称之为类的表达式
eatting = () => {
console.log('eatting')
}
}
const per = new Person('Klaus', 23)
per.running()
per.eatting()
ES6中的constructor函数和实例方法本质上就是对ES5使用构造函数来定义类的形式的一种语法糖形式
唯一的区别是ES5中的构造函数即可以使用new关键字来进行调用,也可以作为一个普通的函数来进行调用
而ES6中的class类只能够使用new关键字来进行调用,不可以作为一个普通函数来进行调用
访问器(或叫存储器)
class Foo {
// 类的私有属性一般使用类的表达式方式来进行定义
// 因为类的私有属性是不可以放置到实例对象上的
// 也就是说不可以挂载到this上,也就不可以在构造函数中对其进行定义
#name = ''
constructor() {
// 在ES6之前,定义私有属性和方法使用的是_开头
// 但这种只是大家约定俗称的一种方法,并没有在语法层面上去其进行限制
// 在ES13中提供了一种新的在语法层面上定义私有属性和私有方法的语法
// 即当一个类的属性或方法使用#开头的时候,就表示该属性或方法是类的私有属性或私有方法
}
// getter方法
get name() {
return this.#name
}
// setter方法
set name(v) {
this.#name = v
}
}
const foo = new Foo()
foo.name = 'Klaus'
console.log(foo.name)
静态方法和静态属性
静态方法通常用于定义直接使用类来执行的方法,不需要有类的实例,使用static关键字来定义
class Foo {
static username = 'klaus'
static bar() {
console.log(this) // => class Foo
}
}
console.log(Foo.username) // => klaus
Foo.bar()
继承
在ES6中新增了使用extends关键字,可以方便的帮助我们实现继承
其本质依旧会被转换为ES5的寄生组合式继承,extends是对于ES5中寄生组合式继承的语法糖
class Parent {
constructor(name, age) {
this.name = name
this.age = age
}
running() {
console.log('running')
}
}
// 使用extends关键字实现类和类之间的继承
class Student extends Parent {
constructor(name, age, sno) {
super(name, age)
this.sno = sno
}
eatting() {
console.log('eatting')
}
}
const stu = new Student('Klaus', 23, 1810166)
console.log(stu)
stu.running()
stu.eatting()
super
class Person {
constructor() {
}
}
class Student extends Person {
// 继承后 对应的默认构造函数如下:
constructor() {
// 在使用this之前或返回实例对象之前
// 必须调用super方法初始化实例对象
super()
}
}
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
running() {
console.log('running')
}
static eatting() {
console.log('eatting')
}
}
// super的使用位置有三个: 子类的构造函数、实例方法、静态方法
class Student extends Person {
constructor(name, age) {
// 在子(派生)类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数
super(name, age)
}
// 子类重写父类的实例方法
running() {
console.log('overwrite')
super.running()
}
// 子类重写父类的静态方法
static eatting() {
console.log('overwrite')
super.eatting()
}
}
const stu = new Student('Klaus', 24)
Student.eatting()
stu.running()
继承内置类
我们也可以让我们的类继承自内置类,比如Array,Math等, 以便于对内置类的功能进行扩展
// 继承内置类方式一
// 重写一个新的类来在继承的同时添加新的属性和方法
class MyArr extends Array {
get lastItem() {
return this[this.length - 1]
}
get firstItem() {
return this[0]
}
}
const arr = new MyArr(10, 20, 30)
console.log(arr.lastItem) // => 30
console.log(arr.firstItem) // => 10
// 继承内置类方式二 - 在原有的类上添加新的属性和方法
Array.prototype.lastItem = function() {
return this[this.length - 1]
}
Array.prototype.firstItem = function() {
return this[0]
}
const arr = new Array(10, 20, 30)
console.log(arr.lastItem()) // => 30
console.log(arr.firstItem()) // => 10
mixin
mixin 本质是一种叫组合模式的设计模式,它通过组合多个功能模块来扩展对象的行为,而不是通过继承来扩展。
mixin 通常通过将一组方法或属性添加到现有对象上来实现扩展。从而在不改变原始对象的情况下添加新功能。
虽然mixin相比继承来说更灵活也更轻量级,但是如果使用了多个mixin,且多个mixin之间 包含同名的属性或方法,后者会覆盖前者。这会导致属性和方法的来源不明确,使代码难以理解和维护,增加调试的复杂性
因此一般不推荐使用mixin
示例
JavaScript默认只支持单继承,但可以通过mixin来模拟多继承
class Student {
studying() {
console.log('studying')
}
}
// mixin模拟多继承的基本原理就是将多继承转变为多层的单继承
function mixinEatting(baseClass) {
return class extends baseClass {
eatting() {
console.log('eatting')
}
}
}
function mixinRunning(baseClass) {
return class extends baseClass {
running() {
console.log('running')
}
}
}
const stu1 = new (mixinEatting(mixinRunning(Student)))()
stu1.running()
stu1.eatting()
stu1.studying()