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)
console.log(ls)

是不是跟构造函数很像?下面我们会讲类与构造函数之间区别,我们先了解下类的基本用法。

类的基本用法

定义类

类是“特殊的函数”,就像定义的函数表达式和函数声明一样,类语法有两个组成部分:类表达式和类声明。

// 类声明
class Point {
    constructor() {
    }
}

// 类表达式
let Point = {
    constructor(){

    }
}

函数声明和类声明之间的一个重要区别,函数声明会提升,类声明不会。需要先声明类,然后再访问它。

// 构造函数会变量提升
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
    }
}

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()

静态方法(属性)

类相当于实例的原型,所有在类中定义的方法(属性),都会被实例继承。如果在一个方法(属性)前,加上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')

尽管静态方法(属性)不能被实例使用,但是父类的静态方法,可以被子类继承(继承那边会介绍)。

私有方法(属性)

私有方法(属性),是只能在类的内部访问的方法和属性,外部不能访问。这也是比较常见的需求,有利于代码的封装。 然而私有方法(属性)的定义之前一直不是很友好,在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关键字,表示这是一个静态的私有方法(属性)。

继承

类可以通过extends关键字实现继承,让子类继承父类的属性和方法。

ES6 规定,子类必须在constructor()方法中调用super(),否则就会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。如果不调用super()方法,子类就得不到自己的this对象。
为什么子类的构造函数,一定要调用super()?

原因就在于 ES6 的继承机制,与 ES5 完全不同。ES5 的继承机制,是先创造一个独立的子类的实例对象,然后再将父类的方法添加到这个对象上面,即“实例在前,继承在后”。ES6 的继承机制,则是先将父类的属性和方法,加到一个空的对象上面,然后再将该对象作为子类的实例,即“继承在前,实例在后”。

子类实例化,父类的构造函数必定会先运行一次

class Person {
    constructor() {
        console.log('person')
    }
}

class Son extends Person {
    constructor() {
        super(); // 调用父类的构造函数
        console.log('son')
    }
}

let son = new Son()
// person
// son

另一个需要注意的地方是,在子类的构造函数中,只有调用super()之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,必须先完成父类的继承,只有super()方法才能让子类实例继承父类。

class Person {
    constructor() {
        console.log('person')
    }
}

class Son extends Person {
    constructor() {
        console.log(this)
        super();
    }
}

let son = new Son()
// 因为this在调用super之前调用了,导致报错
// Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

静态方法(属性)继承

父类的静态属性和静态方法,是可以被子类继承。

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

私有方法(属性)继承

子类无法继承父类的私有方法(属性),只能在定义它的类里面使用。

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

super关键字

super这个关键字,既可以当作函数使用,也可以当作对象使用。

函数

代表父类的构造函数,子类的构造函数必须执行一次super()函数。 作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。

class Person {
    constructor() {
        console.log('person')
    }
}

class Son extends Person {
    constructor() {
        // 必须执行一次
        super();
    }

    say() {
        super()
        // Uncaught SyntaxError: 'super' keyword unexpected here (a
    }
}

对象

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

类与构造函数之间的关系

类是构造函数的另一种写法

function Person(name, age) {
    this.name = name
    this.age = age
}

class ClassPerson {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
}

let son = new Person('zs', 24)
let classSon = new ClassPerson('classZs', 48)
console.log(son)
console.log(classSon)
// 类的数据类型就是函数,类本身就指向构造函数
typeof ClassPerson // "function"
ClassPerson === ClassPerson.prototype.constructor // true

可以看到ClassPerson里面有一个constructor()方法,这就是构造方法,而this关键字则代表实例对象。这种新的 Class 写法,本质上与构造函数Person是一致的。

生成类的实例的写法,与构造函数完全一样,也是使用new命令。但是如果忘记加上new,像函数那样调用ClassPerson(),将会报错,构造函数可以直接调用。

原型方法的表现形式不同

构造函数添加方法到prototype属性,类的所有方法都定义在类的prototype属性,在类的实例上面调用方法,其实就是调用原型上的方法。

// 构造函数
function Person(name, age) {
    this.name = name
    this.age = age
}

// 在原型上增加方法
Person.prototype.say = function () {
    console.log('构造函数在原型上添加方法')
}

// 定义类
class ClassPerson {
    constructor(name, age) {
        this.name = name
        this.age = age
    }

    say() {
        console.log('类在原型上添加方法')
    }
}

let son = new Person('zs', 24)
// 查看实例及原型方法
console.log(son)
let classSon = new ClassPerson('classZs', 48)
// 查看实例及原型方法
console.log(classSon)

类定义say()方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法与方法之间不需要逗号分隔,加了会报错。

构造函数声明会提升,类声明不会

函数声明和类声明之间的一个重要区别在于,函数声明会提升,类声明不会。你首先需要声明你的类,然后再访问它。

// Person {name: 'zs', age: 24}
let son = new Person('zs', 24)
// Uncaught ReferenceError: Cannot access 'classPerson' before initialization
let classSon = new classPerson('classZs', 48)
function Person(name, age) {
    this.name = name
    this.age = age
}

class classPerson {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
}

构造函数与类都可以使用extends继承

类继承

class Person {
    constructor(name, age) {
        this.name = name
        this.age = age
    }

    say() {
        console.log('我会说话')
    }
}

class Son extends Person {
    constructor(name, age) {
        super(name, age);
    }

    run() {
        console.log('我能跑')
    }
}

let zs = new Son('zs', 45)
console.log(zs)

构造函数继承

function Person1(name, age) {
    this.name = name
    this.age = age
}

Person1.prototype.say = function () {
    console.log('我能说话')
}

class Son1 extends Person1 {
    constructor(name, age) {
        super(name, age);
    }

    run() {
        console.log('我能跑')
    }
}

let ls = new Son1('ls', 35)
console.log(ls)

类不能继承常规对象(不可构造的)。如果要继承常规对象,可以改用Object.setPrototypeOf()

// 普通对象
let Person2 = {
    say() {
        console.log('我能说话')
    }
}

// 类不能直接继承普通对象
class Son2 {
    constructor(name, age) {
        this.name = name
        this.age = age
    }

    run() {
        console.log('我能跑')
    }
}

// 类的方法都定义在prototype对象,所以类要继承普通对象,把普通对象设置原型到Son2.prototype即可
Object.setPrototypeOf(Son2.prototype, Person2)
let wu = new Son2('wu', 32)
console.log(wu.say())
// 原型 wu ----> Son2.prototype ----> Person2 ----> Object.prototype ----> null

构造函数没有私有(静态)属性

类有私有(静态)属性的概念,构造函数没有。

class Person {
    // 静态属性
    static count = 100

    // 私有属性
    #name = 'zs'
    // 私有方法
    #say() {
        console.log('我能说话了')
    }

    parentSay() {
        // 调用私有方法
        this.#say()
    }
}

你可能感兴趣的:(es6,es6,javascript)