ES6 class 的使用

94 阅读4分钟

class

class 关键字是ES6中用于声明一个类的关键字, 类的声明格式如下:

// 声明一个类, 名为 Person
class Person {

}

类可以理解为是ES5构造函数的一个语法糖, 上面的类等价于如下ES5构造函数:

function Person() {
  
}

类创建实例同ES5一样使用new关键字

class Person {

}

const p1 = new Person();
console.log(p1); // Person {}

// 判断实例
console.log(p1 instanceof Person); // true
// 原型对象
console.log(p1.__proto__ === Person.prototype); // true
// 构造函数
console.log(p1.__proto__.constructor === Person); // true

实例化传递实例属性

在类中需要定义一个构造函数constructor(固定名称), 每次对该类使用new关键字生成实例时该函数都会自动调用, 它就可以接受外部传递的参数

class Person {
  constructor(name, age) {
    // this 就是当前的实例
    this.name = name;
    this.age = age;
  }
  a = 1; // 所有的 Person 实例都会在自身赋值 a = 1
}

const p1 = new Person("张三", 18);
const p2 = new Person("李四", 20);

console.log(p1); // Person {a: 1, name: '张三', age: 18}
console.log(p2.name); // 李四
console.log(p2.a); // 1

类中定义方法

在类中定义的方法, 跟构造函数类似, 定义一个函数即可, 注意每个函数(方法)不要使用,号分割

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  } // 注意: 这里不要加逗号

  sayHi() {
    const { name, age } = this;
    console.log(`你好, 我叫${name}, 年龄${age}岁`);
  }
}

const p1 = new Person("张三", 18); 
p1.sayHi(); // 你好, 我叫张三, 年龄18岁

const p2 = new Person("李四", 20); 
p2.sayHi(); // 你好, 我叫李四, 年龄20岁

在类中定义的方法, 其实都会定义到该类的原型对象中, class其实就是ES5构造函数的一个语法糖

// 代码同上...

console.log(p1.__proto__.sayHi === Person.prototype.sayHi); // true

Person.prototype.sayHi = function () {
  console.log("通过类的原型对象修改后的 sayHi 方法");
}

p1.sayHi(); // 通过类的原型对象修改后的 sayHi 方法
p2.sayHi(); // 通过类的原型对象修改后的 sayHi 方法

在类的方法中默认开启了严格模式

class Person {
  constructor() {
    Person.test()
  }
  static test() {
    "user strict" // 相当于默认添加这个一行代码

    var a = 1;
    delete a; // SyntaxError: Delete of an unqualified identifier in strict mode.

    b = 2; // ReferenceError: b is not defined
    console.log(window.b);
  }
}
new Person();

getter 和 setter

class中也是可以使用存取器getset

class Person {
  constructor() {
  }
  // 这个属性(value)被`读取时`触发该函数, 其返回值是什么外面读取到的就是什么
  get value() {
    return "getter value";
  }
  // 这个属性(value)被`修改时`时触发该函数, 接受一个参数该参数就是修改的值
  set value(newVal) {
    console.log("setter value", newVal);
  }
}

const p1 = new Person();
console.log(p1.value); // getter value
p1.value = "hello"; // setter value hello

私有属性

在类中可以定义私有的实例属性, 该属性只能在该类中才可以访问, 外界无法访问

class Person {
  // 声明私有变量
  #age;
  constructor(name) {
    this.name = name;
    this.#age = 18;
  }
  getAge() {
    // 在类中可以访问
    return this.#age;
  }
}

const p1 = new Person("张三", 18);
// console.log(p1.#age); // SyntaxError: Private field '#age' must be declared in an enclosing class
console.log(p1.getAge()); // 18

私有属性可以和getter/setter或者提供读取(getXxx)和设置(setXxx)方法一起使用, 以getter/setter为例:

class Person {
  #age;
  constructor(name) {
    this.name = name;
    this.#age = 18;
  }
  get age() {
    return this.#age;
  }
  set age(newVal) {
    if (typeof newVal !== "number") {
      throw "年龄只能是数字";
    } else if (newVal < 0) {
      throw "年龄不能是负数";
    }
    this.#age = newVal;
  }
}

const p1 = new Person("张三");
// console.log(p1.#age); // SyntaxError: Private field '#age' must be declared in an enclosing class
console.log(p1.age); // 18
p1.age = 28;
console.log(p1.age); // 28

静态属性

类中静态属性通过static关键字设置, 被该关键字修饰的属性或者方法只能使用类名去访问

class Person {
  static name = "Person";
  constructor(name) {
    this.name = name;
  }
}

const p1 = new Person("张三");
console.log(p1.name); // 张三
console.log(Person.name); // Person

继承

继承使用extends关键字, 继承可以让一个类(B)获得另一个类(A)定义的属性或方法, 这个A类可以叫基类或者父类, B类可以叫子类

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  sayHi() {
    const { name, age } = this;
    console.log(`你好, 我叫${name}, 年龄${age}岁`);
  }
}

// Student 继承 Person 不写构造函数时, 默认传递接受的所有参数给父类
class Student extends Person { }

const s1 = new Student("张三", 18); 
console.log(s1); // Student {name: '张三', age: 18}
s1.sayHi(); // 你好, 我叫张三, 年龄18岁

console.log(s1 instanceof Student); // true
console.log(s1 instanceof Person); // true

super

在子类中可以使用super关键字来访问和调用父类的构造函数

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  sayHi() {
    const { name, age } = this;
    console.log(`你好, 我叫${name}, 年龄${age}岁`);
  }
}

// Student 继承 Person 不写构造函数时, 默认传递接受的所有参数给父类
class Student extends Person { 
  constructor(name, age, grade) {
    super(name, age);
    // 注意: 子类中使用了 super 后, this 只能在 super 之后才可以访问
    this.grade = grade;
  }
}

const s1 = new Student("张三", 18, "六年级"); 
console.log(s1); // Student {name: '张三', age: 18, grade: '六年级'}

注意: super只能在子类的构造函数和静态方法中使用

class Parent {
  constructor() {
    console.log('Parent constructor');
  }
  static parentStaticFn() {
    console.log('Parent parentStaticFn');
  }
}

class child extends Parent {
  constructor() {
    super() // 可以在子类的构造函数里是可以调用 super 的
    child.childStaticFn();
  }
  static childStaticFn() {
    super.parentStaticFn(); // 也可以在子类的静态方法里是可以访问 super 的
  }
}

new child();

重写方法

子类从父类中继承的方法是可以重写

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  sayHi() {
    const { name, age } = this;
    console.log(`你好, 我叫${name}, 年龄${age}岁`);
  }
}


class Student extends Person {
  constructor(name, age, grade) {
    super(name, age);
    this.grade = grade;
  }
  // 这里的名字同父类的方法同名即可
  sayHi() {
    console.log("我是子类中重写的 sayHi 方法");
  }
}

const s1 = new Student("张三", 18, "六年级");
s1.sayHi(); // 我是子类中重写的 sayHi 方法


// 没有重写的子类还是会使用父类的方法
class Teacher extends Person { }
new Teacher("张麻子", 35).sayHi(); // 你好, 我叫张麻子, 年龄25岁