理解 JavaScript Class 之前,必须先掌握这 3 个基础概念
在学习 Class 之前,有必要先了解以下三个基础概念,这是理解 Class 的关键,也是避免“知其然不知其所以然”的核心。
1. 面向对象编程(OOP)
面向对象编程是一种编程思想,将程序的核心逻辑拆分成一个个对象。每个对象包含属性(存储数据,如人的姓名、年龄)和方法(执行行为,如人的说话、走路)。OOP 有三大核心特性:封装、继承、多态(本文重点讲封装和继承)。其核心优势在于代码复用和易维护性。
2. 原型链
原型链是 JavaScript 的原生继承机制。JS 中所有对象都有一个隐藏的 __proto__ 属性,指向它的原型对象;原型对象也有自己的 __proto__,最终指向 Object.prototype,这条链式结构就是原型链。Class 的底层实现完全依赖原型链,它只是原型链的“表层简化写法”。
3. 语法糖
语法糖是指不改变语言底层逻辑,仅简化代码写法的语法。JS 的 Class 没有新增任何 OOP 功能,只是将 ES5 的“构造函数 + 原型”写法封装成了更直观的结构,让开发更高效。
ES5 的痛点:为什么需要 Class?
在 ES6 之前,实现一个简单的“人”对象模型,需要分开写构造函数(定义属性)和原型(挂载方法),代码分散、语义模糊,新手容易绕晕:
// ES5 构造函数 + 原型实现 OOP
function Person(name) {
this.name = name; // 构造函数定义属性
}
// 原型上挂载方法,所有实例共享
Person.prototype.introduceSelf = function() {
console.log(`Hi! I'm ${this.name}`);
};
// 实例化
const giles = new Person("Giles");
giles.introduceSelf(); // Hi! I'm Giles而 ES6 的 Class 能将属性和方法集中在一个代码块中,写法更符合人类的思维习惯,这也是 Class 出现的核心原因。
一、类与构造器:Class 的基础用法
class 是 ES6 新增的关键字,用于声明一个类。类是创建对象(实例)的“模板”,例如 Person 类可以创建出无数个具体的人(giles、tom 等)。
其中构造器(constructor)是类的核心,专门负责初始化实例的属性,是类中最特殊的方法。
1. 基本类声明语法
一个完整的基础类包含属性声明、构造器、实例方法,结构集中且直观:
// ES6 Class 基础写法
class Person {
name; // 显式声明属性(可选,推荐写,增强可读性)
// 构造器:new 调用类时自动执行
constructor(name) {
this.name = name; // this 指向新建的实例对象
}
// 实例方法:无需 function 关键字,直接写方法名
introduceSelf() {
console.log(`Hi! I'm ${this.name}`);
}
}
// 实例化:new + 类名 → 调用构造器创建实例
const giles = new Person("Giles");
giles.introduceSelf(); // Hi! I'm Giles2. 核心细节
(1)属性的 3 种声明方式
属性声明并非强制要求,但显式声明能让阅读者一眼看到类的所有属性,推荐使用。三种方式均有效:
显式空声明:name;
显式赋默认值:name = '未知';(实例化未传值时用默认值)
隐式声明:不写 name;,直接在构造器中 this.name = name;(构造器会自动创建属性)
(2)构造器的固定作用
构造器是类的唯一特殊方法,不可重名。new 关键字调用类时,会自动执行以下 4 步,无需手动控制:
创建一个空的新对象
将 this 绑定到这个新对象(所以 this 指向实例)
执行构造器代码,为新对象添加属性
返回这个新对象
(3)省略构造器的情况
如果类不需要初始化任何属性,可直接省略 constructor,JavaScript 会自动生成一个无参的默认构造器:
// 无构造器 → 自动生成默认构造器
class Animal {
sleep() {
console.log("zzzzzzz");
}
}
const spot = new Animal(); // 无需传参
spot.sleep(); // zzzzzzz3. 注意事项
类的方法之间无需加逗号,加了会报语法错误(区别于对象字面量)
实例化类必须用 new 关键字,直接调用类(如 Person("Giles"))会报错
类的声明无变量提升,必须先声明类,再实例化(区别于函数声明)
4. 类中的属性和方法默认存储位置
属性:存储在每个实例中,是实例自有的
方法:存储在原型对象中,所有实例共享同一个方法
二、继承:extends + super,实现代码复用
继承是 OOP 的核心特性,指子类(派生类)继承父类(基类)的所有公共属性和方法,同时子类可新增自己的属性/方法,或重写父类的方法。其核心目的是实现代码复用,避免重复编写相同逻辑。
JS 中实现继承的两个核心语法:extends(声明继承关系)、super()(调用父类构造器),缺一不可。
1. 基本继承语法
以 Professor(教授)子类继承 Person(人)父类为例:
// 父类:Person
class Person {
name;
constructor(name) {
this.name = name;
}
introduceSelf() {
console.log(`Hi! I'm ${this.name}`);
}
}
// 子类:Professor 继承 Person → extends 关键字
class Professor extends Person {
teaches; // 子类新增属性
// 子类构造器:接收父类 + 自己的参数
constructor(name, teaches) {
super(name); // 必须先调用 super,传父类构造器的参数
this.teaches = teaches; // 再初始化子类属性
}
// 重写父类的 introduceSelf 方法
introduceSelf() {
console.log(`My name is ${this.name}, I teach ${this.teaches}.`);
}
// 子类新增方法
grade(paper) {
const score = Math.floor(Math.random() * 4 + 1); // 1-4 随机分数
console.log(`论文${paper}分数:${score}`);
}
}
// 实例化子类,需传父类 + 子类的所有参数
const walsh = new Professor("Walsh", "Psychology");
walsh.introduceSelf(); // My name is Walsh, I teach Psychology.
walsh.grade("心理学导论"); // 论文心理学导论分数:32. 继承的核心规则
(1)super() 的强制调用规则
如果子类有自己的构造器,必须在构造器第一行调用 super(),否则直接报错。原因:JS 引擎强制要求先将父类的属性初始化添加到当前 this,才能继续初始化自己的属性。super() 的本质相当于使用 call 方法调用父类构造函数,将 this 指向子类实例:Person.call(this, name);
(2)super() 的传参规则
super() 的参数必须和父类构造器的参数完全一致,父类需要什么参数,super() 就传什么。子类自己的参数在 super() 后单独处理。
(3)方法重写与多态
子类的方法名和父类一致时,会覆盖父类的方法,这是 OOP 多态特性的体现——同一个方法,在父类和子类中有不同的实现,适配不同的场景。
(4)属性/方法的访问规则
子类实例可以访问父类的所有公共属性/方法,也可以访问自己的,遵循就近原则:先找子类自身,找不到再向上找父类。
(5)extends 和 super 的底层原理
extends:相当于使用 Object.create() 继承父类的原型对象
Professor.prototype = Object.create(Person.prototype);super:相当于使用 call 方法调用父类构造函数,将 this 指向子类实例
3. 继承的原型链本质
Class 的继承底层仍是原型链,只是 JS 自动帮我们做了原型链的关联。
三、封装:# 私有字段/方法,实现数据保护
封装是 OOP 的核心特性,指将对象的私有数据隐藏起来,只对外暴露公共的访问方法,避免外部代码随意修改内部属性导致程序逻辑混乱。
JS 的原生封装能力经历了一个发展过程:
ES6 及之前:无原生私有属性,开发者只能通过约定(如属性名加下划线 _year)模拟私有,无法真正阻止外部修改
ES2022(ES13):正式支持原生私有字段/方法,用 # 作为前缀,实现真正的封装
1. 私有字段的使用
私有字段的关键是 # 前缀,声明和使用时都必须加,外部代码访问会直接报语法错误:
// 父类:Person
class Person {
name;
constructor(name) {
this.name = name;
}
}
// 子类:Student,封装 #year 私有字段
class Student extends Person {
#year; // 私有字段:必须显式声明,加 #
constructor(name, year) {
super(name);
this.#year = year; // 初始化私有字段,加 #
}
// 公共方法:类内部可访问私有字段
introduceSelf() {
console.log(`Hi! I'm ${this.name}, I'm in year ${this.#year}.`);
}
// 公共方法:通过业务逻辑控制私有字段的使用
canStudyArchery() {
return this.#year > 1; // 只有年级 > 1 才能学射箭
}
}
// 实例化
const summers = new Student("Summers", 2);
summers.introduceSelf(); // Hi! I'm Summers, I'm in year 2.
summers.canStudyArchery(); // true
// 外部访问私有字段 → 语法错误
summers.#year; // Uncaught SyntaxError2. 私有方法的使用
私有方法同样用 # 前缀,只能在类内部被调用:
class Example {
// 公共方法:对外暴露
somePublicMethod() {
this.#somePrivateMethod(); // 内部调用私有方法
}
// 私有方法:封装内部逻辑
#somePrivateMethod() {
console.log("这是私有方法,仅类内部可调用!");
}
}
const myExample = new Example();
myExample.somePublicMethod(); // 这是私有方法,仅类内部可调用!
myExample.#somePrivateMethod(); // 语法错误,外部无法访问3. 私有成员的核心规则
必须显式声明:私有字段/方法必须在类的顶部声明,不能在构造器中临时创建
# 是标识符的一部分:#year 和 year 是两个完全不同的属性,不可混用
无继承性:子类无法访问父类的私有成员,只能访问父类的公共成员
控制台特殊情况:Chrome 开发者控制台为了调试,放松了私有成员的访问限制,但实际开发中(普通脚本)仍遵循原生规则
4. 封装的核心价值
避免外部代码随意修改内部属性,导致业务逻辑失效
当需要修改私有成员的业务规则时,只需修改类内部的代码,无需修改所有外部调用的代码,大幅降低维护成本
四、JavaScript Class 核心总结
底层本质:Class 是原型链的语法糖,未改变 JS 的原生机制,仅简化写法
核心语法:class 声明类,constructor 初始化属性,extends 声明继承,super() 调用父类构造器,# 定义私有成员
核心特性:完美实现 OOP 的封装、继承、多态,让 JS 的 OOP 更直观
使用优势:代码结构集中、语义清晰、易读易维护,大幅降低 OOP 学习和开发成本
使用场景:需要创建多个具有相同属性/方法的对象时(如用户、商品、课程),用 Class 实现效率最高
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!