ES6之class类
背景知识
在
ES6
的class
没出现之前,我们通过构造函数去创建对象的
下面是构造函数详解
一、什么是类
类是用于创建对象的模板,类只是让对象原型的写法更加清晰、更像面向对象编程的语法。
看个例子
class Person {
// 构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
// 方法
say() {
console.log("我能说话");
}
}
// 实例化
let zs = new Person("张三", 24);
// 实例化
let ls = new Person("李四", 24);
console.log(zs); //Person {name: '张三', age: 24}
console.log(ls); //Person {name: '张三', age: 24}
是不是跟构造函数很像?
下面我们会讲类与构造函数之间区别
我们先了解下类的基本用法。
二、类的基本用法
2.1、定义类
类是“特殊的函数”
就像定义的函数表达式和函数声明一样
类语法有两个组成部分:类表达式和类声明。
// 类声明
class Point {
constructor() {}
}
// 类表达式
let Point = {
constructor() {}
};
2.2、类不会变量提升
函数声明和类声明之间的一个重要区别
函数声明会提升,类声明不会。
需要先声明类,然后再访问它。
// 构造函数会变量提升
let son = new Person("zs", 24);
// Person {name: 'zs', age: 24}
// 类不会变量提升,导致引用异常
let classSon = new ClassPerson("classZs", 48);
// Uncaught ReferenceError: Cannot access 'ClassPerson' before initialization
// 构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 类
class ClassPerson {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
2.3、constructor() 方法
一个类必须有
constructor()
方法,如果没有显式定义,一个空的constructor()
方法会被默认添加。
class Point {}
// 等同于
class Point {
constructor() {}
}
constructor()
方法什么时候被执行呢?在实例化的时候会自动调用该方法。constructor()
方法默认返回实例对象(this)
class Point {
constructor() {
// 通过new命令生成对象实例时,会执行constructor方法
console.log("我执行了");
// 返回的this是实例对象
console.log(this);
}
}
let p = new Point();
类的实例化一定要使用new,否则会报错。这也是跟构造函数的一个主要区别。
// 构造函数
function Point1() {}
// 可以不使用new,当成普通函数执行
let p1 = Point1();
// 类
class Point {
constructor() {
console.log("我执行了");
console.log(this);
}
}
// 类不使用new会报错
// Uncaught TypeError: Class constructor Point cannot be invoked without 'new'
let p = Point();
2.4、静态方法(属性)
类相当于实例的原型,所有在类中定义的方法(属性),都会被实例继承。
如果在一个方法(属性)前,加上
static
关键字,就表示该方法(属性)不会被实例继承,而是直接通过类来调用。
class Person {
static personAge = 28;
constructor(name, age) {
this.name = name;
this.age = age;
}
static getAge(age) {
return this.personAge + age;
}
}
let zs = new Person("zs", 28);
// 静态属性只能通过类来访问
console.log(Person.personAge); // 28
// 静态属性实例不能使用
console.log(zs.personAge); // undefined
// 静态方法只能通过类来访问
Person.getAge(28);
// 静态方法实例不能使用
// zs.getAge();
// Uncaught TypeError: zs.getAge is not a function
// 执行会报错,因为this在严格模式下是underfined
// 这个方法提取出来单独使用,this会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是undefined),从而导致找不到getAge方法而报错。
let getAge = Person.getAge;
getAge(18);
// Uncaught TypeError: Cannot read properties of undefined (reading 'personAge')
尽管静态方法(属性)不能被实例使用,但是父类的静态方法,可以被子类继承(继承那边会介绍)。
2.5、私有方法(属性)
私有方法(属性),是只能在类的内部访问的方法和属性,外部不能访问。
这也是比较常见的需求,有利于代码的封装。 然而私有方法(属性)的定义之前一直不是很友好,在ES2022正式为class添加了私有属性,方法是在属性名之前使用#表示。
class Person {
// 私有属性
#name = "我能说话了";
// 私有方法
#say() {
// 引用私有属性
console.log(this.#name);
}
// 可能这样间接调用私有方法
indirectSay() {
this.#say();
}
}
let p = new Person();
// p.#name
// 报错 Uncaught SyntaxError: Private field '#name' must be declared in an enclosing class
// p.#say()
// 报错 Uncaught SyntaxError: Private field '#say' must be declared in an enclosing class
// 间接调用
p.indirectSay();
// 我能说话了
当然,如果在私有方法(属性)前面加上static关键字,表示这是一个静态的私有方法(属性)。
2.6、类的继承
类可以通过
extends
关键字实现继承,让子类继承父类的属性和方法。
ES6 规定,子类必须在
constructor()
方法中调用super()
,否则就会报错。
这是因为子类自己的
this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。
如果不调用
super()
方法,子类就得不到自己的this对象
。
2.7、super关键字
super
这个关键字,既可以当作函数使用,也可以当作对象使用。
2.7.1、当作函数使用
当作函数使用
super
代表父类的构造函数
子类的构造函数必须执行一次
super()
函数。
作为函数时,
super()
只能用在子类的构造函数之中,用在其他地方就会报错。
class Person {
constructor() {
console.log("person");
}
}
class Son extends Person {
constructor() {
// 必须执行一次
super();
}
say() {
super();
// Uncaught SyntaxError: 'super' keyword unexpected here (a
}
}
2.7.2、当作对象使用。
super
作为对象时,在普通方法中,指向父类的原型对象
class Person {
constructor() {
console.log("person");
}
say() {
console.log("我能说话");
}
}
class Son extends Person {
constructor() {
super();
}
say() {
// 在普通方法中,指向父类的原型对象
super.say();
}
}
//
let son = new Son();
son.say();
// 我能说话
由于
super
指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super
调用的。
class Person {
constructor(name) {
this.name = name;
console.log("person");
}
say() {
console.log("我能说话");
}
}
class Son extends Person {
constructor(name) {
super(name);
}
say() {
console.log(super.name);
}
}
//
let son = new Son("zs");
// 由于name是父类的实例属性,不是原型属性,使用super获取不到
son.say();
// undefined
2.7、静态方法(属性)继承
通过super()方法,子类可以继承父类的静态方法(属性)
class Person {
static count = 100;
static obj = { name: "zs obj" };
constructor() {
// 实例化时被调用
console.log("person");
}
say() {
console.log("我能说话了");
}
}
class Son extends Person {
constructor() {
super();
}
}
// 子类继承了父类的静态属性
Son.count--;
Son.obj.name = "son obj";
// 子类继承了父类的静态属性是浅拷贝,如果父类的静态属性的值是一个对象,那么子类的静态属性也会指向这个对象,因为浅拷贝只会拷贝对象的内存地址。
console.log("Son.count:%s", Son.count);
// Son.count:99
console.log("Son.obj:%o", Son.obj);
// Son.obj:{name: 'son obj'}
console.log("Person.count:%s", Person.count);
// Person.count:100
console.log("Person.obj:%o", Person.obj);
// Person5.obj:{name: 'son obj'}
// Object.getPrototypeOf()方法可以用来从子类上获取父类。
Object.getPrototypeOf(Son) === Person;
2.8、私有方法(属性)继承
子类无法通过
super()
继承父类的私有方法(属性)
class Foo {
#p = 1;
#m() {
console.log("hello");
}
}
class Bar extends Foo {
constructor() {
super();
console.log(this.#p); // 报错
this.#m(); // 报错
}
}
但是子类可以通过父类的方法间接访问父类的私有方法(属性),说白了还是不能直接访问呗,只能通过定义它的类来使用。
class Foo {
#p = 1;
getP() {
return this.#p;
}
}
class Bar extends Foo {
constructor() {
super();
console.log(this.getP()); // 1
}
}