Class:面向对象编程的语法糖

ES6 引入的 Class 语法,本质是 JavaScript 原型链继承的语法糖—— 它没有改变 JS 基于原型的面向对象本质,但提供了更简洁、更符合传统 OOP 语言(如 Java、C#)的语法形式,让代码结构更清晰、可读性更强。

本文将从核心概念、语法细节、继承机制、高级特性四个维度,全面解析 ES6 Class。

一、为什么需要 Class?—— 原型链的 “痛点”

在 ES6 之前,JS 通过构造函数 + 原型链实现面向对象,但存在明显问题:

  1. 语法松散:构造函数、原型方法、静态方法的定义分散,代码结构混乱;
  2. 语义模糊:prototype__proto__ 概念抽象,新手难以理解;
  3. 继承复杂:实现继承需要手动绑定 prototype、修正 constructor,易出错(如 Child.prototype = new Parent() 可能继承父类实例属性)。

ES6 Class 正是为解决这些问题而生 —— 它将原型链的底层逻辑封装成直观的语法,让开发者无需关注 prototype 细节,就能高效编写面向对象代码。

二、Class 核心语法:从定义到实例化

1. 基本定义与实例化

Class 的定义有两种形式:类声明(不可提升)和 类表达式(可匿名 / 命名)。

(1)类声明(Class Declaration)

// 类声明(不可提升,类似 let/const)
class Person {
  // 构造函数:实例化时执行,用于初始化实例属性
  constructor(name, age) {
    this.name = name; // 实例属性(每个实例独立)
    this.age = age;
  }

  // 原型方法(挂载到 Person.prototype,所有实例共享)
  sayHi() {
    console.log(`Hi, I'm ${this.name}, ${this.age} years old`);
  }

  // 静态方法(挂载到类本身,通过类调用,不被实例继承)
  static createAdult(name) {
    return new Person(name, 18); // 静态方法可创建实例
  }
}

// 实例化:必须用 new 关键字(否则报错)
const alice = new Person("Alice", 20);
alice.sayHi(); // 输出:Hi, I'm Alice, 20 years old

// 调用静态方法
const bob = Person.createAdult("Bob");
bob.sayHi(); // 输出:Hi, I'm Bob, 18 years old

(2)类表达式(Class Expression)

// 匿名类表达式
const Animal = class {
  constructor(type) {
    this.type = type;
  }
  roar() {
    console.log(`${this.type} is roaring`);
  }
};

// 命名类表达式(仅在类内部可访问类名)
const Dog = class DogClass {
  constructor(name) {
    this.name = name;
  }
  getClassName() {
    return DogClass.name; // 内部可访问 DogClass
  }
};

const dog = new Dog("Wangwang");
console.log(dog.getClassName()); // 输出:DogClass
console.log(DogClass); // 报错:DogClass is not defined(外部不可访问)

2. 关键细节:Class 与原型链的对应关系

Class 语法本质是原型链的封装,二者的对应关系如下(以上述 Person 类为例):

Class 语法原型链底层逻辑
constructor 方法对应构造函数 Person
sayHi() 原型方法等价于 Person.prototype.sayHi = function() {}
static createAdult()等价于 Person.createAdult = function() {}
new Person() 实例化本质是 new 构造函数,实例的 __proto__ 指向 Person.prototype

验证:

const alice = new Person("Alice", 20);
console.log(alice.__proto__ === Person.prototype); // true(实例原型指向类原型)
console.log(alice.sayHi === Person.prototype.sayHi); // true(实例方法来自原型)
console.log(Person.createAdult === Person.prototype.createAdult); // false(静态方法在类本身)

三、Class 继承:extends 与 super

ES6 用 extends 关键字实现继承,比 ES5 的 “原型链继承 + 构造函数继承” 简洁得多,且支持继承原生构造函数(如 ArrayDate)。

1. 基本继承

// 父类
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  sayHi() {
    console.log(`Hi, ${this.name}`);
  }
  static info() {
    console.log("This is Person class");
  }
}

// 子类继承父类
class Student extends Person {
  // 子类构造函数:必须先调用 super(),再使用 this
  constructor(name, age, studentId) {
    super(name, age); // 调用父类构造函数(初始化 name、age)
    this.studentId = studentId; // 子类实例属性
  }

  // 重写父类原型方法
  sayHi() {
    super.sayHi(); // 调用父类原型方法
    console.log(`My student ID is ${this.studentId}`);
  }

  // 子类静态方法
  static study() {
    super.info(); // 调用父类静态方法
    console.log("Students are studying");
  }
}

// 实例化子类
const tom = new Student("Tom", 18, "S1001");
tom.sayHi(); 
// 输出:
// Hi, Tom
// My student ID is S1001

// 调用子类静态方法
Student.study();
// 输出:
// This is Person class
// Students are studying

// 验证继承关系
console.log(tom instanceof Student); // true
console.log(tom instanceof Person); // true(子类实例也是父类实例)
console.log(Student.__proto__ === Person); // true(子类的原型指向父类,静态方法继承)
console.log(Student.prototype.__proto__ === Person.prototype); // true(子类原型的原型指向父类原型,实例方法继承)

2. super 的核心作用

super 是继承中的关键,有两种用法:

  • 作为函数:仅能在子类 constructor 中调用,代表父类构造函数,用于初始化父类属性(必须先调用 super(),否则子类 this 未定义);
  • 作为对象
    • 在原型方法中:指向父类原型对象Person.prototype),可调用父类原型方法(如 super.sayHi());
    • 在静态方法中:指向父类本身Person),可调用父类静态方法(如 super.info())。

⚠️ 注意:

  • 子类没有 constructor 时,会默认生成一个并调用 super(...arguments)
  • 若子类有 constructor,必须显式调用 super(),且不能在 super() 前使用 this

3. 继承原生构造函数

ES5 无法继承原生构造函数(如 Array),因为原生构造函数的内部属性无法通过原型链继承;但 ES6 extends 可以:

// 继承 Array,创建一个带额外方法的数组类
class MyArray extends Array {
  // 新增方法:获取数组中大于 n 的元素
  filterGreaterThan(n) {
    return this.filter(item => item > n);
  }
}

const arr = new MyArray(1, 3, 5, 7);
console.log(arr.filterGreaterThan(3)); // [5,7](继承 Array 的 filter 方法)
console.log(arr instanceof MyArray); // true
console.log(arr instanceof Array); // true(仍是数组实例)

四、Class 高级特性

1. 实例属性的新写法(ES2022+)

传统写法中,实例属性需在 constructor 中通过 this.xxx 定义;ES2022 支持直接在类体顶层定义实例属性(更简洁):

class Person {
  // 直接定义实例属性(无需写在 constructor 中)
  name = "Unknown";
  age = 0;

  constructor(name) {
    this.name = name; // 可覆盖默认值
  }
}

const alice = new Person("Alice");
console.log(alice.name); // Alice
console.log(alice.age); // 0

2. 私有属性与方法(# 前缀)

ES2022 引入私有属性 / 方法,用 # 前缀标识,仅能在类内部访问,外部无法读取或修改(安全性更高):

class Person {
  #secret; // 私有属性(必须声明)
  name;

  constructor(name, secret) {
    this.name = name;
    this.#secret = secret; // 初始化私有属性
  }

  #privateMethod() { // 私有方法
    return this.#secret;
  }

  getSecret() {
    return this.#privateMethod(); // 类内部可访问私有方法/属性
  }
}

const alice = new Person("Alice", "I like cats");
console.log(alice.name); // Alice
console.log(alice.#secret); // 报错:Private field '#secret' must be declared in an enclosing class
console.log(alice.#privateMethod()); // 报错:私有方法不可外部调用
console.log(alice.getSecret()); // I like cats(通过公有方法间接访问)

3. getter 与 setter

用于封装属性的读取和赋值逻辑,类似 ES5 的 Object.defineProperty,但语法更简洁:

class Person {
  constructor(name) {
    this._name = name; // 约定 _ 前缀为“受保护”属性(非真正私有)
  }

  // getter:读取 name 时触发
  get name() {
    return this._name.trim(); // 处理:去除空格
  }

  // setter:修改 name 时触发
  set name(newName) {
    if (!newName) throw new Error("Name cannot be empty"); // 校验:非空
    this._name = newName;
  }
}

const alice = new Person(" Alice ");
console.log(alice.name); // Alice(getter 处理)

alice.name = " Bob ";
console.log(alice.name); // Bob(setter 校验 + getter 处理)

alice.name = ""; // 报错:Name cannot be empty(setter 校验失败)

4. 静态属性(ES2022+)

静态属性是类本身的属性(非实例属性),ES2022 支持直接在类体顶层用 static 定义:

class MathUtil {
  static PI = 3.1415926; // 静态属性
  static add(a, b) { // 静态方法
    return a + b;
  }
}

console.log(MathUtil.PI); // 3.1415926
console.log(MathUtil.add(2, 3)); // 5

5. 类的动态方法 / 属性

Class 支持通过计算属性名定义动态方法 / 属性(用 [] 包裹表达式):

const methodName = "sayHi";
const propName = "age";

class Person {
  [propName] = 18; // 动态实例属性

  [methodName]() { // 动态原型方法
    console.log(`Hi, I'm ${this[propName]} years old`);
  }

  static [methodName + "Static"]() { // 动态静态方法
    console.log("Static sayHi");
  }
}

const alice = new Person();
console.log(alice.age); // 18
alice.sayHi(); // Hi, I'm 18 years old
Person.sayHiStatic(); // Static sayHi

五、Class 与 ES5 构造函数的区别

特性ES6 ClassES5 构造函数 + 原型链
语法形式简洁、结构化,类似传统 OOP 语言松散,需手动操作 prototype
提升行为不可提升(类似 let/const)函数声明可提升
实例化要求必须用 new(否则报错)可不用 new(this 指向全局,易出错)
继承机制extends 直接继承,支持原生构造函数需组合原型链 + 构造函数,不支持原生继承
私有成员支持(# 前缀)无原生支持(依赖闭包模拟,不彻底)
静态成员原生支持 static 关键字需手动给构造函数赋值(如 Person.staticMethod = ...

六、总结

ES6 Class 是原型链的语法糖,核心价值在于:

  1. 简化面向对象代码的编写,降低理解成本;
  2. 统一类的定义、继承、静态成员等语法;
  3. 支持私有属性、原生构造函数继承等高级特性(ES2022+ 增强)。

使用建议:

  • 编写面向对象代码时,优先使用 Class 语法(替代 ES5 构造函数);
  • 理解 Class 与原型链的对应关系,避免 “语法糖依赖”(遇到复杂场景需懂底层逻辑);
  • 结合 static、私有成员、getter/setter 等特性,提升代码的安全性和可读性。

虽然 Class 封装了原型链,但 JS 本质仍是基于原型的语言 ——Class 没有引入新的面向对象模型,只是让原型链的使用更优雅。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

canjun_wen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值