class

81 阅读5分钟

一、class是什么?

在 JavaScript 中,class 是 ES6(ECMAScript 2015)引入的语法糖(syntactic sugar),本质上是基于 JavaScript 原型继承(Prototype Inheritance)  的一种更清晰、更接近传统面向对象语言的写法。它的底层实现仍然是原型和构造函数,但语法更加简洁易读。

类比
想象类就像一张 “饼干模具”,模具决定了饼干的形状(属性)和图案(方法),用这个模具压出来的饼干(对象)都长得一样。


二、class 的本质?

class 的本质

  • class 不是一种新的类型,而是对原有原型继承机制的封装。

  • 它通过更直观的语法定义构造函数和原型方法,例如:

     // ES5 传统写法(构造函数 + 原型)
     function Person(name) {
       this.name = name;
     }
     Person.prototype.sayHello = function() {
       console.log(`Hello, I'm ${this.name}`);
     };
    
     // ES6 class 写法
     class Person {
       constructor(name) {
         this.name = name;
       }
       sayHello() {
         console.log(`Hello, I'm ${this.name}`);
       }
     }
    

    两者完全等价,class 只是语法上的改进。


三、class 的特点

  • 构造函数:通过 constructor 方法定义。
  • 方法定义:类方法直接写在类内部,自动绑定到原型(prototype)。
  • 不可枚举:类方法默认不可枚举(而 ES5 原型方法默认可枚举)。
  • 必须用 new 调用:直接调用类会报错(而传统构造函数不强制 new)。
  • 继承语法:通过 extends 和 super 简化继承(比 ES5 的 Object.create 和手动绑定原型更清晰)。

四、为什么要用类?

  • 代码复用:避免重复写相同的代码。
  • 结构清晰:将数据(属性)和操作(方法)封装在一起。
  • 继承:子类可以继承父类的特性,扩展功能。

五、class的知识点

1. 基本结构

class Animal {
  // 属性:
  // 用于初始化对象的属性。
  // 在创建对象时自动调用。
  constructor(name) {
    this.name = name; // this 指向当前对象
  }
  
  // 方法:
  // 定义在类中的函数,供对象调用
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

// 创建对象(实例化):
// 用 `new` 关键字创建对象
const dog = new Animal("Dog");
dog.speak(); // "Dog makes a noise."

2. 继承(extends 和 super

可以继承父类的属性和方法,并扩展自己的功能。

  • extends 关键字

    // 父类
    class Animal {
      constructor(name) {
        this.name = name;
      }
      eat() {
        console.log(`${this.name}在吃饭`);
      }
    }
    
    // 子类继承父类
    class Dog extends Animal {
      bark() {
        console.log("汪汪!");
      }
    }
    
    // 使用
    const myDog = new Dog("阿黄");
    myDog.eat();  // 输出:阿黄在吃饭(继承自父类)
    myDog.bark(); // 输出:汪汪!(子类自己的方法)
    
  • super 关键字

    (1) 子类构造函数中调用父类构造函数

    如果子类有自己的构造函数(constructor),必须在构造函数内 第一行 调用 super(),否则会报错。
    这是因为子类需要通过 super 初始化父类的属性和方法,才能正确构建对象。

    class Animal {
      constructor(name) {
        this.name = name;
      }
    }
    
    class Dog extends Animal {
      constructor(name, breed) {
        super(name); // 必须先调用父类构造函数!不然会报错(必须在访问 this 前调用 super())
        this.breed = breed;
      }
    }
    
    const myDog = new Dog("阿黄", "金毛");
    console.log(myDog.name);  // "阿黄"(来自父类)
    console.log(myDog.breed); // "金毛"(来自子类)
    

    (2) 子类方法中调用父类方法

    当子类需要 扩展(而非完全覆盖)父类方法 时,可以先用 super.method() 调用父类方法,再添加子类特有的逻辑。

     class Animal {
       speak() {
         console.log("动物发出声音");
       }
     }
    
     class Dog extends Animal {
       speak() {
         super.speak(); // 先调用父类的 speak()
         console.log("汪汪!"); // 再添加子类逻辑
       }
     }
    
     const dog = new Dog();
     dog.speak();
     // 输出:
     // 动物发出声音
     // 汪汪!
    

    (3)调用父类的静态方法

    如果父类有静态方法,子类可以通过 super 调用它们。

    class Animal {
     static info() {
       return "这是动物类";
     }
    }
    
    class Dog extends Animal {
     static info() {
       return super.info() + " -> 这是子类狗";
     }
    }
    
    console.log(Dog.info()); 
    // 输出:
    // "这是动物类 -> 这是子类狗"
    

    总结:何时必须使用 super

场景用法目的
子类构造函数中super(参数)初始化父类属性,确保子类正确继承
子类方法中扩展父类方法super.方法名(参数)复用父类逻辑,避免重复代码
子类调用父类静态方法super.静态方法名(参数)复用父类静态方法逻辑

3. 静态方法 static

  • 属于类本身的方法,不能通过实例调用。
  • 常用于工具函数:
class MathUtils {
  static add(a, b) {
    return a + b;
  }
}

console.log(MathUtils.add(2, 3)); // 5(直接通过类调用)

4. 私有字段 #

  • # 开头的属性是私有的,只能在类内部访问:
class BankAccount {
  #balance = 0; // 私有属性

  deposit(amount) {
    this.#balance += amount;
  }

  getBalance() {
    return this.#balance;
  }
}

const account = new BankAccount();
account.deposit(100);
console.log(account.getBalance()); // 100
// account.#balance; // 报错!

5. Getter 和 Setter

控制属性的读取和修改:

class Person {
  constructor(name) {
    this._name = name; // 使用下划线约定表示“内部属性”
  }

  // Getter:读取 name 时触发
  get name() {
    return this._name.toUpperCase(); // 返回大写的名字
  }

  // Setter:修改 name 时触发
  set name(newName) {
    this._name = newName.trim(); // 自动去除首尾空格
  }
}

// 使用示例
const p = new Person(" 小明 ");
console.log(p.name); // 输出 "小明"(自动调用 getter)

p.name = " 小红 ";    // 自动调用 setter
console.log(p.name); // 输出 "小红"(setter 处理后)

其他问题解答

Q1:如果父类没有构造函数,子类需要写 super() 吗?

需要!即使父类没有显式定义 constructor,JavaScript 会隐式生成一个空的构造函数。子类仍需调用 super()

class Parent {} // 隐式有 constructor() {}

class Child extends Parent {
  constructor() {
    super(); // 必须调用!
  }
}

Q2:能否在异步函数或回调中使用 super

不能super 的调用必须在构造函数或方法内部直接执行,且必须在访问 this 之前调用。

Q3:为什么 super 必须在构造函数的第一行调用?

JavaScript 要求子类必须先通过 super 初始化父类,才能访问 this(确保继承链正确建立)。

Q4: class与它的传统原型有什么区别?

特性ES5 构造函数ES6 class
方法可枚举性默认可枚举默认不可枚举
调用方式可不加 new(可能出错)必须加 new
继承语法手动操作原型链(复杂)extends 和 super(简洁)
代码结构分散(构造函数 + 原型方法)集中(类内部统一)