ES6之class类

2023-10-19  本文已影响0人  扶得一人醉如苏沐晨

背景知识

ES6class没出现之前,我们通过构造函数去创建对象的

下面是构造函数详解

一、什么是类

类是用于创建对象的模板,类只是让对象原型的写法更加清晰、更像面向对象编程的语法。

看个例子

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
  }
}

三、类与构造函数之间的关系

上一篇下一篇

猜你喜欢

热点阅读