JS进阶 | 详解ES6中的Class

8,431 阅读7分钟

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

JS进阶系列文章

👇 阅读本文你将学习到 👇

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

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

class

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

class Animal {

}

// or

const Animal = class {

}

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

function Animal(){

}

类的构造函数

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

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

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

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

var a = new Animal("ABC");
console.log(a); // Animal { name: 'ABC' }

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

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

console.log(Animal === Animal.prototype.constructor); // true

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

类中的属性

实例属性

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

class Animal{
  constructor(name,height,weight) {
    this.name = name;
    this.height = height
    this.weight = weight
  }
}

静态属性

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

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

class Foo{
  static name ='_island'
}

console.log(Foo.name);

私有属性

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

class Animal{
  #age;
  constructor(name,age){
    this.name=name
    this.#age=age
  }
}

var a = new Animal('_island',18)
console.log(a); // Animal { name: '_island' }
console.log(a.name); // _island
console.log(a.age); // undefined
console.log(a.#age); // Private field '#age' must be declared in an enclosing class

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

console.log(Object.getOwnPropertyDescriptors(a))

{
  name: {
    value: '_island',
    writable: true,
    enumerable: true,
    configurable: true
  }
}

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

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

类中的方法

实例方法

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

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

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

class Animal{
  eating() {
    console.log(this.name + " eating");
  }
}

静态方法

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

class Animal{
  static createName(name) {
    return name
  }
}

var a2 = Animal.createName("_island");
console.log(a2); // _island

私有方法

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

class Foo {
  __getBloodType() {
    return "O";
  }
}

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

类的继承

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

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

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

extends的使用

class Animal{

}

// dog 继承 Animal 类
class dog extends Animal {

}

继承类的属性和方法

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

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

class Animal {
  constructor(name) {
    this.name = name;
  }
  eating() {
    console.log(this.name + " eating");
  }
}

// dog 继承 Animal 类
class dog extends Animal {
  constructor(name, legs) {
    super(name);
    this.legs = legs;
  }
  speaking() {
    console.log(this.name + " speaking");
  }
}

var d = new dog("tom", 4);
d.eating(); // tom eating
d.speaking(); // tom speaking
console.log(d.name); // tom

Super

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

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

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

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

class Animal{
  constructor(name)
}

class dog{
  constructor(name,type,weight){
    super(name)
    this.type=type
    this.weight=weight
  }
}

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

class Animal {
  constructor(name) {
    this.name = name;
  }
  eating() {
    console.log(this.name + " eating");
  }
}

// dog 继承 Animal 类
class dog extends Animal {
  constructor(name, legs) {
    super(name);
    this.legs = legs;
  }
  speaking() {
    super.eating()
    console.log(this.name + " speaking");
  }
  
}

var d = new dog("tom",4);
d.speaking(); // tom eating tom speaking

Getter 和 Setter

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

class Animal {
  constructor() {
    this._age = 3;
  }

  get age() {
    return this._age;
  }

  set age(val) {
    this._age = val;
  }
}

var a = new Animal();
console.log(a.age); // 3
a.age = 4;
console.log(a.age); //4

关于class扩展

严格模式

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

name属性

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

class Animal{

}
console.log(Animal.name); // Animal

变量提升

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

new Foo(); // ReferenceError
class Foo {}

总结

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