如何声明一个类?类如何继承?

如何声明一个类?类如何继承?------ JavaScript 类与继承完全指南

一、开篇:类 ------JavaScript 面向对象编程的核心载体

在 JavaScript 的发展历程中,从早期的构造函数 + 原型链 实现面向对象,到 ES6 引入class关键字提供更规范、更贴近传统面向对象语言的类语法,类已经成为构建可复用、可维护、层次化代码的核心载体。

无论是 React/Vue 中的组件开发、Node.js 中的模块封装,还是复杂业务逻辑的抽象,都离不开类的声明与继承。很多初学者容易混淆 "类的声明方式""原型方法与实例方法的区别",也会在继承中遇到 "构造函数调用""方法重写""超类访问" 等问题,导致写出的代码层次混乱、难以扩展。

本文将从类的声明(基础到进阶)类的继承(核心方法与细节)继承的实战场景与避坑指南三个维度,结合完整示例深度拆解 JavaScript 类与继承的精髓,帮你彻底掌握类的声明技巧与继承逻辑,写出符合现代前端开发规范的面向对象代码。

二、第一部分:如何声明一个类?(从基础到进阶)

ES6 提供的class语法是声明类的标准方式(本质是构造函数 + 原型链的语法糖,但更简洁、更具可读性),同时也保留了传统的 "构造函数 + 原型" 声明方式,我们重点讲解现代开发中主流的class声明法,同时补充传统方式作为底层理解参考。

1. 基础声明:使用class关键字(核心方式)

(1)核心语法

使用class关键字声明类,类名遵循首字母大写 的约定俗成(区分普通函数,提升代码可读性),核心包含constructor构造函数和类方法(原型方法)。

javascript

运行

复制代码
// 基础类声明语法
class ClassName {
  // 1. 构造函数:初始化实例属性(可选,若省略会默认生成空构造函数)
  constructor(param1, param2) {
    // 实例属性:通过this绑定,每个实例拥有独立的属性副本
    this.prop1 = param1;
    this.prop2 = param2;
  }

  // 2. 原型方法:类的核心方法,所有实例共享该方法(挂载在原型上)
  method1() {
    // 方法逻辑中可通过this访问实例属性和其他方法
    return `实例属性:${this.prop1},${this.prop2}`;
  }

  // 3. 多个原型方法:用逗号分隔(ES6语法中可省略逗号,更简洁)
  method2() {
    console.log("这是类的另一个原型方法");
  }
}
(2)实战示例:声明一个User

javascript

运行

复制代码
// 声明一个User类
class User {
  // 构造函数:初始化用户名和年龄
  constructor(name, age) {
    this.name = name; // 实例属性:用户名
    this.age = age;   // 实例属性:年龄
  }

  // 原型方法:展示用户信息
  showInfo() {
    console.log(`你好,我是${this.name},今年${this.age}岁`);
  }

  // 原型方法:修改用户年龄(带逻辑校验)
  setAge(newAge) {
    if (typeof newAge === "number" && newAge >= 0 && newAge <= 150) {
      this.age = newAge;
      console.log(`年龄修改成功,当前年龄:${this.age}岁`);
    } else {
      console.log("输入年龄不合法,请输入0-150之间的数字");
    }
  }
}

// 实例化类:使用new关键字创建类的实例(必须带new,否则报错)
const user1 = new User("张三", 25);
const user2 = new User("李四", 28);

// 调用实例的原型方法
user1.showInfo(); // 输出:你好,我是张三,今年25岁
user1.setAge(26); // 输出:年龄修改成功,当前年龄:26岁

user2.showInfo(); // 输出:你好,我是李四,今年28岁
// 验证:所有实例共享原型方法(内存复用,提升性能)
console.log(user1.showInfo === user2.showInfo); // 输出:true
(3)核心要点说明
  1. constructor构造函数
  • 用于初始化实例属性,是类的 "入口",当使用new创建实例时,会自动调用constructor
  • 可选省略,若省略,JS 引擎会默认生成一个空的constructor()constructor() {});
  • 内部通过this绑定的属性是实例属性,每个实例拥有独立的副本,互不干扰;
  • 不能返回非对象类型的值(若返回对象,会覆盖new创建的实例,不推荐)。
  1. 类方法(原型方法)
  • 类内部直接定义的方法(如showInfo())是原型方法 ,挂载在类的prototype属性上;
  • 所有实例共享该方法,无需为每个实例创建独立的方法副本,节省内存;
  • 方法内部的this指向调用该方法的实例(若单独提取方法,需注意this绑定问题)。
  1. 实例化类
  • 必须使用new关键字调用类,否则会抛出TypeError(类不能像普通函数一样直接调用);
  • 实例化时传入的参数,会直接传递给constructor构造函数。

2. 进阶声明:类的其他成员(静态方法、私有成员、get/set 访问器)

除了基础的构造函数和原型方法,现代 JavaScript 类还支持静态方法、私有成员、get/set访问器等高级特性,进一步丰富类的功能。

(1)静态方法:使用static关键字(类本身的方法)

静态方法是挂载在类本身的方法,不能被实例调用,只能通过类名调用,常用于定义工具方法、创建实例的工厂方法。

javascript

运行

复制代码
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  showInfo() {
    console.log(`你好,我是${this.name},今年${this.age}岁`);
  }

  // 静态方法:用static关键字声明
  static createAdmin() {
    // 静态方法内部可返回类的实例(工厂方法)
    return new User("系统管理员", 0);
  }

  // 静态工具方法:格式化用户信息
  static formatUser(user) {
    return `[用户信息] 姓名:${user.name},年龄:${user.age}`;
  }
}

// 1. 调用静态方法创建实例(不能通过实例调用)
const admin = User.createAdmin();
admin.showInfo(); // 输出:你好,我是系统管理员,今年0岁

// 2. 调用静态工具方法
const formatStr = User.formatUser(admin);
console.log(formatStr); // 输出:[用户信息] 姓名:系统管理员,年龄:0

// 3. 实例调用静态方法:报错(静态方法不属于实例)
console.log(admin.createAdmin); // 输出:undefined
// admin.createAdmin(); // 抛出:TypeError: admin.createAdmin is not a function
(2)私有成员:#前缀(ES2022+,受保护的成员)

私有成员(私有属性、私有方法)仅能在类内部访问,外部和子类都无法直接访问,用于保护类的内部状态,避免外部意外篡改。

javascript

运行

复制代码
class User {
  // 私有属性:用#前缀声明(必须在类内部提前声明)
  #password; // 私有属性:用户密码

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

  showInfo() {
    console.log(`你好,我是${this.name},今年${this.age}岁`);
  }

  // 私有方法:用#前缀声明(仅类内部可调用)
  #checkPassword(pwd) {
    return this.#password === pwd;
  }

  // 公共方法:间接访问私有成员(对外暴露规范接口)
  verifyPassword(inputPwd) {
    const isCorrect = this.#checkPassword(inputPwd);
    console.log(isCorrect ? "密码验证通过" : "密码验证失败");
    return isCorrect;
  }
}

const user = new User("张三", 25, "123456");

// 1. 调用公共方法间接访问私有成员
user.verifyPassword("123456"); // 输出:密码验证通过
user.verifyPassword("654321"); // 输出:密码验证失败

// 2. 外部直接访问私有成员:报错(私有成员对外隐藏)
console.log(user.#password); // 抛出:SyntaxError: Private field '#password' must be declared in an enclosing class
// user.#checkPassword("123456"); // 抛出:SyntaxError: Private field '#checkPassword' must be declared in an enclosing class
(3)get/set访问器:优雅操作实例属性

getset访问器用于定义实例属性的 "读取" 和 "修改" 逻辑,允许我们在访问 / 修改属性时添加额外处理(如校验、格式化),让属性操作更优雅、更安全。

javascript

运行

复制代码
class User {
  constructor(name, age) {
    this.name = name;
    this._age = age; // 约定:下划线前缀表示"受保护属性"(仅规范,非语法层面私有)
  }

  // get访问器:读取age属性时触发
  get age() {
    console.log("正在读取年龄属性");
    return this._age;
  }

  // set访问器:修改age属性时触发
  set age(newAge) {
    console.log("正在修改年龄属性");
    if (typeof newAge === "number" && newAge >= 0 && newAge <= 150) {
      this._age = newAge;
    } else {
      console.log("输入年龄不合法,无法修改");
    }
  }
}

const user = new User("张三", 25);

// 1. 读取age属性:触发get访问器
console.log(user.age); // 输出:正在读取年龄属性 → 25

// 2. 修改age属性:触发set访问器
user.age = 26; // 输出:正在修改年龄属性
console.log(user.age); // 输出:正在读取年龄属性 → 26

// 3. 传入不合法年龄:触发set访问器的校验逻辑
user.age = -10; // 输出:正在修改年龄属性 → 输入年龄不合法,无法修改

3. 补充:传统声明方式(构造函数 + 原型链)

在 ES6 之前,没有class关键字,开发者通过 "构造函数 + 原型链" 实现类的功能,这是class语法的底层实现,理解该方式有助于深入掌握类的本质。

javascript

运行

复制代码
// 1. 构造函数:对应class的constructor
function User(name, age) {
  this.name = name;
  this.age = age;
}

// 2. 原型方法:对应class的原型方法,所有实例共享
User.prototype.showInfo = function() {
  console.log(`你好,我是${this.name},今年${this.age}岁`);
};

User.prototype.setAge = function(newAge) {
  if (typeof newAge === "number" && newAge >= 0 && newAge <= 150) {
    this.age = newAge;
  }
};

// 3. 实例化:与class一致,使用new关键字
const user = new User("张三", 25);
user.showInfo(); // 输出:你好,我是张三,今年25岁

// 验证:class本质是构造函数的语法糖
console.log(typeof User); // 输出:function
console.log(User.prototype === user.__proto__); // 输出:true

三、第二部分:类如何继承?(核心方法与细节)

继承是面向对象编程的三大特性之一,用于实现代码复用和层次化抽象------ 子类(派生类)可以继承父类(超类、基类)的属性和方法,同时可以扩展自己的专属功能,或重写父类的方法。

JavaScript 中类的继承核心是使用extends关键字(实现类的继承)和super关键字(访问父类成员),这是现代开发的标准方式。

1. 基础继承:使用extends关键字(实现父类属性与方法的继承)

(1)核心语法

javascript

运行

复制代码
// 父类(超类、基类)
class ParentClass {
  constructor(param1) {
    this.parentProp = param1;
  }

  parentMethod() {
    console.log("这是父类的原型方法");
    return this.parentProp;
  }
}

// 子类(派生类):使用extends关键字继承父类
class ChildClass extends ParentClass {
  constructor(param1, param2) {
    // 必须先调用super():初始化父类构造函数(否则报错)
    super(param1);
    // 再初始化子类自己的实例属性
    this.childProp = param2;
  }

  // 子类自己的原型方法(扩展功能)
  childMethod() {
    console.log("这是子类的原型方法");
    return this.childProp;
  }
}
(2)实战示例:AdminUser子类继承User父类

javascript

运行

复制代码
// 父类:User类
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  showInfo() {
    console.log(`姓名:${this.name},年龄:${this.age}`);
  }

  setAge(newAge) {
    if (typeof newAge === "number" && newAge >= 0 && newAge <= 150) {
      this.age = newAge;
      console.log(`年龄修改为:${this.age}岁`);
    } else {
      console.log("年龄输入不合法");
    }
  }
}

// 子类:AdminUser类(继承User类)
class AdminUser extends User {
  constructor(name, age, role) {
    // 1. 调用super():传入父类构造函数所需的参数,初始化父类属性
    super(name, age);
    // 2. 初始化子类专属属性
    this.role = role; // 子类专属属性:管理员角色(如"超级管理员"、"普通管理员")
  }

  // 3. 子类专属方法:扩展父类没有的功能
  showRole() {
    console.log(`角色:${this.role}`);
  }
}

// 实例化子类
const admin = new AdminUser("系统管理员", 0, "超级管理员");

// 1. 调用继承自父类的方法
admin.showInfo(); // 输出:姓名:系统管理员,年龄:0
admin.setAge(1); // 输出:年龄修改为:1岁

// 2. 调用子类自己的专属方法
admin.showRole(); // 输出:角色:超级管理员

// 3. 验证:子类实例同时拥有父类和子类的属性
console.log(admin.name, admin.age, admin.role); // 输出:系统管理员 1 超级管理员
(3)核心要点说明
  1. extends关键字
  • 用于声明子类继承父类,子类会自动继承父类的所有公共实例属性原型方法(私有成员无法继承);
  • 支持链式继承(如GrandChild extends Child extends Parent),但不推荐过深的继承链(增加代码复杂度)。
  1. super关键字的核心作用(必须掌握)
  • 在子类构造函数中,必须先调用super() ,才能使用this关键字(否则会抛出ReferenceError);
  • super()本质是调用父类的构造函数,需传入父类构造函数所需的参数;
  • 若子类省略constructor,JS 引擎会默认生成一个构造函数,自动调用super()并传递所有参数(constructor(...args) { super(...args); })。

2. 进阶继承:方法重写与super访问父类方法

子类不仅可以继承父类的方法,还可以重写(覆盖)父类的方法 (实现子类专属的逻辑),同时可以通过super.方法名()在重写的方法中访问父类的原方法,实现 "扩展父类方法" 而非 "完全覆盖"。

(1)方法重写:子类覆盖父类的方法

javascript

运行

复制代码
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  showInfo() {
    console.log(`【父类方法】姓名:${this.name},年龄:${this.age}`);
  }
}

class AdminUser extends User {
  constructor(name, age, role) {
    super(name, age);
    this.role = role;
  }

  // 方法重写:子类覆盖父类的showInfo()方法
  showInfo() {
    console.log(`【子类方法】姓名:${this.name},年龄:${this.age},角色:${this.role}`);
  }
}

const admin = new AdminUser("系统管理员", 0, "超级管理员");
admin.showInfo(); // 输出:【子类方法】姓名:系统管理员,年龄:0,角色:超级管理员
(2)扩展父类方法:通过super访问父类原方法

很多场景下,我们不需要完全覆盖父类方法,而是在父类方法的基础上扩展功能,此时可以通过super.方法名()调用父类的原方法。

javascript

运行

复制代码
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  showInfo() {
    console.log(`姓名:${this.name},年龄:${this.age}`);
  }
}

class AdminUser extends User {
  constructor(name, age, role) {
    super(name, age);
    this.role = role;
  }

  // 扩展父类方法:先调用父类原方法,再添加子类专属逻辑
  showInfo() {
    // 调用父类的showInfo()方法(通过super访问)
    super.showInfo();
    // 子类专属扩展逻辑
    console.log(`角色:${this.role}`);
  }
}

const admin = new AdminUser("系统管理员", 0, "超级管理员");
admin.showInfo();
// 输出结果:
// 姓名:系统管理员,年龄:0
// 角色:超级管理员

3. 特殊继承:静态方法的继承与私有成员的继承规则

(1)静态方法的继承

子类会自动继承父类的静态方法,可通过子类名.静态方法名()调用,无需额外配置。

javascript

运行

复制代码
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  // 父类静态方法
  static formatUser(user) {
    return `[用户] 姓名:${user.name},年龄:${user.age}`;
  }
}

class AdminUser extends User {
  constructor(name, age, role) {
    super(name, age);
    this.role = role;
  }
}

const admin = new AdminUser("系统管理员", 0, "超级管理员");

// 子类继承父类的静态方法,通过子类名调用
const formatStr = AdminUser.formatUser(admin);
console.log(formatStr); // 输出:[用户] 姓名:系统管理员,年龄:0
(2)私有成员的继承规则

父类的私有成员(#前缀声明)无法被子类继承,子类既不能直接访问父类的私有成员,也不能重写父类的私有方法,私有成员仅在定义它的类内部可见。

javascript

运行

复制代码
class User {
  #password; // 父类私有属性

  constructor(name, age, password) {
    this.name = name;
    this.age = age;
    this.#password = password;
  }

  // 父类私有方法
  #checkPassword(pwd) {
    return this.#password === pwd;
  }

  // 父类公共方法:间接访问私有成员
  verifyPassword(pwd) {
    return this.#checkPassword(pwd);
  }
}

class AdminUser extends User {
  constructor(name, age, password, role) {
    super(name, age, password);
    this.role = role;
  }

  // 子类尝试访问父类私有成员:报错
  showPassword() {
    // console.log(this.#password); // 抛出:SyntaxError: Private field '#password' must be declared in an enclosing class
  }
}

const admin = new AdminUser("系统管理员", 0, "123456", "超级管理员");
// 子类可调用父类公共方法,但无法直接访问父类私有成员
console.log(admin.verifyPassword("123456")); // 输出:true

四、第三部分:继承的实战避坑指南

1. 坑 1:子类构造函数中未先调用super(),使用this报错

问题现象 :子类构造函数中,在调用super()之前使用this,会抛出ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

解决方案 :子类构造函数中,必须先调用super(),再初始化子类的实例属性(使用thissuper()是初始化父类实例的关键,只有父类实例初始化完成,子类才能安全地使用this

javascript

运行

复制代码
class AdminUser extends User {
  constructor(name, age, role) {
    // 错误:先使用this,后调用super()
    // this.role = role; // 抛出错误
    // 正确:先调用super(),再使用this
    super(name, age);
    this.role = role;
  }
}

2. 坑 2:重写父类方法时,忽略super导致父类逻辑丢失

问题现象 :子类重写父类方法时,直接编写子类逻辑,未调用super.方法名(),导致父类方法中的核心逻辑丢失,出现功能异常。

解决方案 :若需要扩展父类方法而非完全覆盖,务必在子类重写的方法中调用super.方法名(),保留父类核心逻辑。

3. 坑 3:过深的继承链导致代码难以维护

问题现象 :使用链式继承(如A extends B extends C extends D),继承链过深,当需要修改某个类的逻辑时,会影响所有子类,且代码追踪困难。

解决方案

  1. 控制继承链深度,尽量不超过 3 层;
  2. 优先使用 "组合" 而非 "继承" 实现功能复用(如将公共逻辑封装为模块,在类中引入使用);
  3. 遵循 "里氏替换原则":子类必须能够替换父类,且不改变父类的核心行为。

4. 坑 4:混淆实例方法与静态方法的继承规则

问题现象:尝试通过子类实例调用父类的静态方法,导致报错。

解决方案 :明确静态方法的调用规则 ------静态方法属于类本身,只能通过类名调用,不能通过实例调用,子类继承的静态方法也遵循该规则。

五、总结:类与继承的核心知识点回顾与实战准则

  1. 类的声明核心
  • 现代开发优先使用class关键字,核心包含constructor构造函数(初始化实例属性)和原型方法(所有实例共享);
  • 高级特性:static静态方法(类本身的方法)、#私有成员(类内部保护)、get/set访问器(优雅操作属性)。
  1. 类的继承核心
  • 基础继承:使用extends关键字实现子类继承父类,子类自动继承父类的公共实例属性和原型方法;
  • 关键细节:子类构造函数中必须先调用super(),才能使用this
  • 进阶操作:方法重写(覆盖父类方法)、super访问父类方法(扩展父类功能)。
  1. 实战准则
  • 类名首字母大写,方法命名语义化,提升代码可读性;
  • 优先使用组合而非继承实现功能复用,控制继承链深度;
  • 私有成员用于保护内部状态,对外暴露规范的公共接口;
  • 重写父类方法时,按需保留父类逻辑(通过super),避免功能丢失。

类与继承是 JavaScript 面向对象编程的核心,掌握它们不仅能写出更具层次化、可复用的代码,更能深入理解现代前端框架的底层设计思想(如 React 的组件继承、Vue 的混入机制)。

最后用一句话总结:类是抽象业务逻辑的载体,继承是实现代码复用的桥梁,合理声明类、优雅使用继承,才能构建出健壮、可扩展的前端应用

相关推荐
企微自动化14 小时前
企业微信 API 开发:如何实现外部群消息主动推送
java·开发语言·spring
love530love14 小时前
EPGF 新手教程 04一个项目一个环境:PyCharm 是如何帮你“自动隔离”的?(全 GUI,新手零命令)
运维·开发语言·ide·人工智能·python·pycharm
2501_9419820514 小时前
企业微信 API 外部群主动推送技术解析
前端·chrome
艾莉丝努力练剑14 小时前
【QT】初识QT:背景介绍
java·运维·数据库·人工智能·qt·安全·gui
Grassto14 小时前
Go 在哪里找第三方包?Module 查找顺序详解
开发语言·后端·golang
小鸡脚来咯14 小时前
后端开发vue速成
开发语言·前端·javascript
糯诺诺米团14 小时前
C++多线程打包成so给JAVA后端(Ubuntu)<2>
java·开发语言·c++
-西门吹雪14 小时前
c++线程之再研究研究多线程
开发语言·c++
一线大码14 小时前
后端分层架构规范和标准包结构
java·后端