类:构造函数的语法糖
传统的构造函数的问题
- 属性和原型方法定义分离,降低了可读性
- 原型成员可以被枚举
- 默认情况下,构造函数仍然可以被当作普通函数使用
类的写法
在es6的class中一个类的静态方法,私有属性,原型方法均写在了一起,解决了传统构造函数的属性原型分离的问题
class Plane {
// 静态属性,相当于Plane.alive
static alive() {
return true
}
constructor(name) { // 这里是参数
// 私有属性
this.name = name || '普通飞机';
this.blood = 100;
}
// 原型方法 forin不可枚举此方法
fly() {
console.log('fly')
}
}
类的特点
- 类声明不会被提升,与 let 和 const 一样,存在暂时性死区
console.log(Plane);
class Plane {
constructor(type) {
this.type = type
}
}
// Uncaught ReferenceError: Cannot access 'Plane' before initialization
- 类中的所有代码均在严格模式下执行
class Plane {
constructor(type) {
this.type = type;
a = 1
}
}
new Plane()
// Uncaught ReferenceError: a is not defined
- 类的所有原型方法都是不可枚举的(私有方法可枚举)
class Plane {
constructor(type) {
this.type = type; // 私有属性可枚举
this.fn = () => {}; // 私有方法可枚举
}
fly() {console.log('fly')} // 原型方法不可枚举
}
let p = new Plane();
for (const key in p) {
console.log(key) // type fn
}
- 类的所有方法都无法被当作构造函数使用(包括静态方法)
class Plane {
static fn () { // 静态方法不可new
this.name = 'a'
}
constructor(type) {
this.type = type;
}
fly() {console.log('fly')} // 原型方法不可new
}
let p = new Plane();
new Plane.fn(); // Uncaught TypeError: Plane.fn is not a constructor
new p.fly(); // Uncaught TypeError: p.fly is not a constructor
- 类的构造器必须使用 new 来调用
class Plane {
constructor(type) {
this.type = type;
}
}
Plane(); // Uncaught TypeError: Class constructor Plane cannot be invoked without 'new'
类的其他书写方式
- 可计算属性名
let flyName = 'fly'
class Plane {
constructor(type) {
this.type = type;
}
[flyName]() {console.log('fly')} // 计算属性名
}
let p = new Plane();
p[flyName]()
- getter和setter
// 假设飞机寿命只能为0-10年
class Plane {
constructor(type, age) {
this.type = type;
this.age = age
}
set age(age) {
if(age < 0) {
this._age = 0;
} else if (age > 10) {
this._age = 10
} else {
this._age = age
}
}
get age() {
return this._age + '岁'
}
}
let p = new Plane('plane', 2);
console.log(p.age) // 2岁
- 静态成员
class Plane {
static canFly = true;
constructor(type, age) {
this.type = type;
}
}
console.log(Plane.canFly); // true
- 字段初始化器
class Plane {
constructor(type) {
this.type = type;
}
name = '飞机'; // name属性是私有属性
// 这种写法会将print变为私有方法
// 由于使用了箭头函数,箭头函数没有this,所以this永远为实例对象
print = () => {
console.log(this.type)
}
}
let p = new Plane('普通飞机');
p.print(); // 普通飞机
let print = p.print; // 即使改变执行环境,也会输出p的type
print(); // 普通飞机
- 类表达式
let a = class { // 等同于 class a {...}
constructor() {
this.a = 1;
this.b = 2;
}
}
console.log(new a())
继承
继承有两个新增关键字extends和super
extends用于继承
super两个用法
在constructor里面要先调用super(向父类传入所需参数)
在方法里面使用super,super代表父类的原型
用法如下
class Animal {
constructor(type, name, age, sex) {
this.type = type;
this.name = name;
this.age = age;
this.sex = sex;
}
print() {
console.log(`【种类】:${this.type}`);
console.log(`【名字】:${this.name}`);
console.log(`【年龄】:${this.age}`);
console.log(`【性别】:${this.sex}`);
}
}
class Dog extends Animal {
constructor(name, age, sex) {
super('犬类', name, age, sex); // 继承的子类在constructor里面先调super方法,传入父类所需参数
this.like = '吃骨头'; // 然后添加子类的特有属性(不能写在super前面)
}
print() { // 覆盖父类方法
super.print(); // 调用父类方法使用super
console.log(`【爱好】:${this.like}`);
}
}
const a = new Dog("旺财", 3, "男");
a.print()
冷知识
一般情况下,父类是不可以直接new的,创建实例对象是通过子类构造函数来创建,所以可以给父类构造函数做一个处理
class Animal {
constructor(type, name, age, sex) {
if (new.target == Animal) { // 可以通过new.target获得创建实例的构造函数
throw('不能直接通过Animal创建实例')
}
this.type = type;
this.name = name;
this.age = age;
this.sex = sex;
}
print() {
console.log(`【种类】:${this.type}`);
console.log(`【名字】:${this.name}`);
console.log(`【年龄】:${this.age}`);
console.log(`【性别】:${this.sex}`);
}
}
class Dog extends Animal {
constructor(name, age, sex) {
super('犬类', name, age, sex); // 继承的子类在constructor里面先调super方法,传入父类所需参数
this.like = '吃骨头'; // 添加子类的特有属性
}
print() { // 覆盖父类方法
super.print(); // 调用父类方法使用super
console.log(`【爱好】:${this.like}`);
}
}