美文网首页
ES6之class类

ES6之class类

作者: 扶得一人醉如苏沐晨 | 来源:发表于2023-10-19 09:20 被阅读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
      }
    }
    

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

    相关文章

      网友评论

          本文标题:ES6之class类

          本文链接:https://www.haomeiwen.com/subject/jzanbdtx.html