「这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战

JS进阶系列文章

👇 阅读本文你将学习到 👇

  • 类中的构造函数
  • 类的实例、静态、私有属性
  • 类的实例、静态、私有方法
  • 类的继承
  • Getter and Setter
  • 关于class一些扩展知识点

ES6ECMAScript6)之前,JavaScript语法中是不支持类的,导致面向对象编程方法无法直接使用,但我们可以通过function来实现模拟出类,而随着JavaScript的更新,在ES6出现了中出现class关键字,可以用于定义类。接下来让我们看看它的如何使用的。

class

下面我们来看看如何使用class关键字声明一个类。

 1class Animal {
 2
 3}
 4
 5
 6
 7const Animal = class {
 8
 9}

而在ES6之前,我们都是通过以下这样子的方式来模拟出类的。

 1function Animal(){
 2
 3}

类的构造函数

每一个类都可以有一个自己的构造函数,这个名称是固定的constructor,当我们通过new调用一个类时,这个类就会调用自己的constructor方法(构造函数)。

  • 它用于创建对象时给类传递一些参数
  • 每一个类只能有一个构造函数,否则报错

通过new调用一个类时,会调用构造函数,执行如下操作过程:

  1. 在内存中开辟一块新的空间用于创建新的对象
  2. 这个对象内部的__proto__属性会被赋值为该类的prototype属性
  3. 构造函数内的this,指向创建出来的新对象
  4. 执行构造函数的内部代码
  5. 如果函数没有返回对象,则返回this
 1class Animal  {
 2  
 3  
 4  constructor(name) {
 5    this.name = name;
 6  }
 7}
 8
 9var a = new Animal("ABC");
10console.log(a); 

上面这个例子中,我们在class中定义的constructor,这个就是构造方法,而this代表的是实例对象。

这个class,你可以把它看作构造函数的另外一种写法,因为它和它的构造函数的相等的,即是类本身指向构造函数。

 1console.log(Animal === Animal.prototype.constructor); 

其实,在类上的所有方法都会放在prototype属性上。

类中的属性

实例属性

实例的属性必须定义在类的方法里,就如上面的例子,我们在构造函数中定义name这个属性。

 1class Animal{
 2  constructor(name,height,weight) {
 3    this.name = name;
 4    this.height = height
 5    this.weight = weight
 6  }
 7}

静态属性

当我们把一个属性赋值给类本身,而不是赋值给它prototype,这样子的属性被称之为静态属性(static)。

静态属性直接通过类来访问,无需在实例中访问。

 1class Foo{
 2  static name ='_island'
 3}
 4
 5console.log(Foo.name);

私有属性

私有属性只能在类中读取、写入,不能通过外部引用私有字段。

 1class Animal{
 2  #age;
 3  constructor(name,age){
 4    this.name=name
 5    this.#age=age
 6  }
 7}
 8
 9var a = new Animal('_island',18)
10console.log(a); 
11console.log(a.name); 
12console.log(a.age); 
13console.log(a.#age); 

我们通过getOwnPropertyDescriptors方法获取到它的属性,同样也是获取不到。

 1console.log(Object.getOwnPropertyDescriptors(a))
 2
 3{
 4  name: {
 5    value: '_island',
 6    writable: true,
 7    enumerable: true,
 8    configurable: true
 9  }
10}

私有字段仅能在字段声明中预先定义。

公共和私有字段声明是JavaScript标准委员会TC39提出的实验性功能(第3阶段)。浏览器中的支持是有限的,但是可以通过Babel等系统构建后使用此功能。

类中的方法

实例方法

ES6之前,我们定义类中的方法是类中的原型上进行定义的,防止类中的方法重复在多个对象上。

 1function Animal() {}
 2Animal.prototype.eating = function () {
 3  console.log(this.name + " eating");
 4};

ES6中,定义类中的方法更加简洁,直接在类中定义即可,这样子的写法即优雅可读性也强。

 1class Animal{
 2  eating() {
 3    console.log(this.name + " eating");
 4  }
 5}

静态方法

静态方法与上面提到的静态属性是一样的,在方法前面使用static关键字进行声明,之后调用这个方法时不需要通过类的实例来调用,可以直接通过类名来调用它。

 1class Animal{
 2  static createName(name) {
 3    return name
 4  }
 5}
 6
 7var a2 = Animal.createName("_island");
 8console.log(a2); 

私有方法

在面向对象中,私有方法是一个常见需求,但是在ES6中没有提供,我们可以通过某个方法来实现它。

 1class Foo {
 2  __getBloodType() {
 3    return "O";
 4  }
 5}
 6

需要注意的是,通过下划线开头通常我们会局限它是一个私有方法,但是在类的外部还是可以正常调用到这个方法的

类的继承

extends关键字用于扩展子类,创建一个类作为另外一个类的一个子类。

它会将父类中的属性和方法一起继承到子类的,减少子类中重复的业务代码。

这对比之前在ES5中修改原型链实现继承的方法的可读性要强很多,而且写法很简洁。

extends的使用

 1class Animal{
 2
 3}
 4
 5
 6class dog extends Animal {
 7
 8}

继承类的属性和方法

下面这个例子,我们定义了dog这个类,通过extends关键字继承了Animal类的属性和方法。

在子类的constructor方法中,我们使用了super关键字,在子类中它是必须存在的,否则新建实例时会抛出异常。这是因为子类的this对象是继承自父类的this对象,如果不调用super方法,子类就得不到this对象。

 1class Animal {
 2  constructor(name) {
 3    this.name = name;
 4  }
 5  eating() {
 6    console.log(this.name + " eating");
 7  }
 8}
 9
10
11class dog extends Animal {
12  constructor(name, legs) {
13    super(name);
14    this.legs = legs;
15  }
16  speaking() {
17    console.log(this.name + " speaking");
18  }
19}
20
21var d = new dog("tom", 4);
22d.eating(); 
23d.speaking(); 
24console.log(d.name); 

Super

super关键字用于访问和调用一个对象的父对象上的函数。

super指的是超级、顶级、父类的意思

在子类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数。

下面这段代码,子类的constructor方法中先调用了super方法,它代表了父类的构造函数,也就是说我们把参数传递进去之后,其实它是调用了父类的构造函数。

 1class Animal{
 2  constructor(name)
 3}
 4
 5class dog{
 6  constructor(name,type,weight){
 7    super(name)
 8    this.type=type
 9    this.weight=weight
10  }
11}

下面这段代码使用super调用父类的方法

 1class Animal {
 2  constructor(name) {
 3    this.name = name;
 4  }
 5  eating() {
 6    console.log(this.name + " eating");
 7  }
 8}
 9
10
11class dog extends Animal {
12  constructor(name, legs) {
13    super(name);
14    this.legs = legs;
15  }
16  speaking() {
17    super.eating()
18    console.log(this.name + " speaking");
19  }
20  
21}
22
23var d = new dog("tom",4);
24d.speaking(); 

Getter 和 Setter

在类内部也可以使用getset关键字,对应某个属性设置存值和取值函数,拦截属性的存取行为。

 1class Animal {
 2  constructor() {
 3    this._age = 3;
 4  }
 5
 6  get age() {
 7    return this._age;
 8  }
 9
10  set age(val) {
11    this._age = val;
12  }
13}
14
15var a = new Animal();
16console.log(a.age); 
17a.age = 4;
18console.log(a.age); 

关于class扩展

严格模式

在类和模块的内部,默认是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。

name属性

ES6中的类只是ES5构造函数的一层包装,所以函数的许多属性都被class继承了,包括name属性。

 1class Animal{
 2
 3}
 4console.log(Animal.name); 

变量提升

class不存在变量提升,这与我们在ES5中实现类的不同的,function关键字会存在变量提升。

 1new Foo(); 
 2class Foo {}

总结

ES6之后,我们在定义类以及它内部的属性方法,还有继承操作的语法变得非常简洁且易懂,class是一个语法糖,其内部还是通过ES5中的语法来实现的。且有些浏览器不支持class语法,我们可以通过babel来进行转换。

个人笔记记录 2021 ~ 2025