
在JavaScript中,Class是ES6引入的一种语法糖,它基于原型继承机制,提供了更接近传统面向对象语言的写法。下面我们将深入探讨JavaScript中的Class,包括其定义、继承、静态方法、私有字段等。
JavaScript是一门基于原型(Prototype)的编程语言,而非传统的基于类(Class)的语言。在ES6引入Class语法之前,JavaScript完全通过原型链实现对象间的继承关系。
ES6引入的Class语法并不是 JavaScript 中新增的继承模型,而是基于原型继承的语法糖(Syntactic Sugar),使代码更易读、更接近传统面向对象语言的写法。
Class的基本定义
使用class关键字可以定义一个类,类名通常首字母大写。类中可以包含构造函数constructor、实例方法、静态方法等。
1. 基本定义
// 定义一个基本的类
class Person {
// 构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法
sayHello() {
return `Hello, my name is ${this.name}, and I'm ${this.age} years old.`;
}
}
// 实例化
const person = new Person('青癸', 20);
console.log(person.sayHello()); // 输出: Hello, my name is 青癸, and I'm 20 years old.Class的本质:
Class语法仍然基于原型继承实现。
constructor方法对应ES5中的构造函数。
Class内部定义的方法会被添加到类的prototype上。
static关键字定义的方法直接附加在类本身上。
Class的实例化必须使用new关键字。
2. 继承
使用 extends 关键字实现继承,子类可以继承父类的属性和方法。子类的构造函数中必须调用 super() 来初始化父类的构造函数。
class Student extends Person {
// 构造函数
constructor(name, age, grade) {
super(name, age); // 调用父类的constructor
this.grade = grade;
}
// 重写父类方法
sayHello() {
console.log(`Hello, my name is ${this.name} and I'm in grade ${this.grade}`);
}
// 子类特有方法
study() {
console.log(`${this.name} is studying`);
}
}
const student = new Student('黑龙', 21, 10);
student.sayHello(); // 输出: Hello, my name is 黑龙 and I'm in grade 12
student.study(); // 输出: 黑龙 is studyingclass Parent {
constructor(name) {
this.name = name;
}
sayHello() {
return `Hello from ${this.name}`;
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 1. 作为函数调用父类构造函数
this.age = age;
}
sayHello() {
// 2. 作为对象调用父类方法
return `${super.sayHello()}, I'm ${this.age} years old`;
}
}Class继承的本质
extends关键字建立了子类与父类之间的原型链关系。
super关键字用于调用父类的构造函数和方法。
子类的prototype.__proto__指向父类的prototype。
子类本身的__proto__指向父类。
方法重写通过在子类原型上定义同名方法实现。
3. 静态方法
静态方法属于类本身,而不是类的实例。它们通常用于实现与类相关的功能,但不依赖于实例的数据。静态方法可以被子类继承,也可以通过子类调用。
class MyClass {
static staticMethod() {
console.log('Static method called');
}
}
MyClass.staticMethod(); // 输出: Static method called
class ChildClass extends MyClass {}
ChildClass.staticMethod(); // 输出: Static method calledclass MathUtils {
static PI = 3.14159;
// 静态方法
static max(...args) {
return Math.max(...args);
}
// 静态块 (ES2022)
static {
console.log('MathUtils类已加载');
}
}
// MathUtils类已加载
console.log(MathUtils.max(1, 5, 3)); // 5
console.log(MathUtils.PI); // 3.141594. 私有方法和私用属性
ES2022引入了真正的私有方法和私有属性,使用#前缀表示。私有属性和方法只能在类内部访问,外部无法直接访问。
class BankAccount {
// 私有字段
#balance = 0;
// 私有静态字段
static #bankCode = 'BANK001';
constructor(initialBalance) {
this.#balance = initialBalance;
}
// 私有方法
#validateAmount(amount) {
if (amount <= 0) throw new Error('Amount must be positive');
return true;
}
deposit(amount) {
this.#validateAmount(amount);
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
// 静态方法访问私有静态字段
static getBankCode() {
return this.#bankCode;
}
}
const account = new BankAccount(100);
account.deposit(50);
console.log(account.getBalance()); // 150
// console.log(account.#balance); // 报错:私有字段无法访问
// 需要放置script标签中,浏览器控制台不会报错,具体原因不明5. Getter和Setter
可以使用 get 和 set 关键字来定义获取和设置属性值的方法,从而允许对属性进行更精细的控制。
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
// 使用getter定义计算属性area
get area() {
return this.width * this.height;
}
// 使用setter对宽度进行验证
set width(newWidth) {
if (newWidth > 0) {
this._width = newWidth;
} else {
console.error('Width must be positive');
}
}
get width() {
return this._width;
}
}
const rect = new Rectangle(10, 20);
console.log(rect.area); // 输出: 200
rect.width = -5; // 输出: Width must be positive6. 类表达式
类也可以使用表达式的方式定义,类似于函数表达式。
// 匿名类表达式
const Person = class {
constructor(name) {
this.name = name;
}
};
// 命名类表达式
const Employee = class EmployeeClass {
constructor(name) {
this.name = name;
}
getName() {
// EmployeeClass只在类内部可见
return EmployeeClass.name;
}
};7. 注意事项
类声明和类表达式都不会被提升(hoisting),因此必须先定义后使用。
类中的所有方法默认都是不可枚举的(non-enumerable)。
类中的代码默认以严格模式执行。
类内部默认严格模式。
没有私有方法(ES2022之前),使用约定或WeakMap模拟。
避免在构造函数中返回对象。
8. 与构造函数的对比
在ES5中,我们通常使用构造函数和原型来模拟类。ES6的Class语法更简洁,更易理解。
特性 | Class声明 | 传统构造函数 |
|---|---|---|
语法清晰度 | 高(像Java) | 低(原型链操作) |
严格模式 | 自动严格模式 | 需要手动开启 |
提升(Hoisting) | 不完全提升(有暂时性死区) | 存在变量提升 |
原型修改 | 更安全,不易被外部篡改 | 容易被意外修改 |
// ES5
function PersonES5(name) {
this.name = name;
}
PersonES5.prototype.sayHello = function() {
console.log('Hello, ' + this.name);
};
// ES6
class PersonES6 {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello, ${this.name}`);
}
}9. 继承内置类
Class语法还可以用来继承JavaScript的内置类,比如Array、Error等。
class MyArray extends Array {
// 可以添加自定义方法
first() {
return this[0];
}
last() {
return this[this.length - 1];
}
}
const arr = new MyArray(1, 2, 3);
console.log(arr.first()); // 输出: 1
console.log(arr.last()); // 输出: 310. 类与原型链的关系
JavaScript的class是ES6引入的语法糖,本质上仍然是基于原型的继承,但提供了更接近传统面向对象语言的语法。
class Animal {}
class Rabbit extends Animal {}
console.log(Rabbit.prototype.__proto__ === Animal.prototype); // true
console.log(Rabbit.__proto__ === Animal); // true
// instanceof 操作符
const rabbit = new Rabbit();
console.log(rabbit instanceof Rabbit); // true
console.log(rabbit instanceof Animal); // true
console.log(rabbit instanceof Object); // true关键点
每个函数都有一个prototype属性,指向一个对象。
通过new关键字创建的实例,其__proto__属性指向构造函数的prototype。
实例可以访问原型链上的所有属性和方法。
instanceof运算符通过检查原型链来判断对象类型。
11. Mixin模式
JavaScript中的Mixin模式是一种代码复用机制,允许将一个或多个对象的属性和方法"混入"(复制)到另一个对象中,从而实现功能复用,避免了传统继承的复杂性(如多重继承的问题)。
核心思想:
通过"复制"而非"继承"的方式,将多个独立的功能模块组合到目标对象上,使对象可以灵活地拥有多种特性。
在JavaScript中,Mixin主要通过以下几种方式实现:
1. 使用 Object.assign()(ES6+)
最常用的现代实现方式,直接将源对象的属性复制到目标对象:
// 定义Mixin对象(功能模块)
const canEat = {
eat() {
console.log('正在吃东西');
}
};
const canSleep = {
sleep() {
console.log('正在睡觉');
}
};
// 创建目标对象
const person = {
name: '孙雪'
};
// 将Mixin混入目标对象
Object.assign(person, canEat, canSleep);
// 使用混入的方法
person.eat(); // 正在吃东西
person.sleep(); // 正在睡觉2. 原型链Mixin
将Mixin添加到构造函数的原型上,使所有实例都能共享这些方法:
// 定义构造函数
function Animal(name) {
this.name = name;
}
// 定义Mixin
const canRun = {
run() {
console.log(`${this.name}正在跑`);
}
};
// 将Mixin混入原型链
Object.assign(Animal.prototype, canRun);
// 创建实例
const dog = new Animal('小柴');
dog.run(); // 小柴正在跑3. 自定义Mixin函数(传统方式)
更灵活地控制混入过程,比如过滤某些属性或处理冲突:
function mixin(target, ...sources) {
sources.forEach(source => {
// 只复制自身属性(不包括原型链)
Object.keys(source).forEach(key => {
// 可以添加冲突处理逻辑
if (!(key in target)) {
target[key] = source[key];
}
});
});
return target;
}
// 使用自定义Mixin
const person = mixin({}, canEat, canSleep, {
work() {
console.log('正在工作');
}
});优缺点
优点
灵活的代码复用,避免继承层级过深。
实现类似"多重继承"的效果。
功能模块独立,易于维护。
缺点
可能导致方法名冲突(需额外处理)。
无法直接追踪属性来源,调试较复杂。
引用类型的属性会被共享(注意深拷贝问题)
总结
JavaScript的class语法虽然看起来像传统面向对象编程,但底层仍然是基于原型的。理解这一本质对于掌握JavaScript至关重要。Class提供了更清晰的语法结构,使得代码更易读、更易维护,是现代JavaScript开发中的重要工具。
彩蛋
完整示例代码,请移步Cnb: 《深入理解JavaScript:Class的本质与原型继承》