【javascript】js中关于Class(类)的介绍和使用
'# 【javascript】js中关于Class(类)的介绍和使用
一、背景与问题
JavaScript 作为动态语言,在 ES6 之前主要通过构造函数和原型链实现面向对象编程。这种模式虽然功能强大,但存在以下问题:
- 构造函数和原型链的分离不够直观,容易造成认知混乱
- 方法定义需要重复使用
prototype属性 - 缺乏对私有属性和访问控制的自然支持
- 无法直接使用
class关键字定义类
ES6 引入的类语法(class)解决了这些问题,提供了更直观的面向对象编程方式。但理解其底层原理、合理使用场景以及避免常见陷阱,对实际开发至关重要。
二、基本原理
1. 类的内部机制
ES6 的类本质上是基于原型的封装。每个类定义会创建一个构造函数,其原型链指向 Function.prototype。关键特性包括:
- 构造函数:
class关键字定义的函数,用于初始化实例 - 原型链:通过
prototype属性实现继承 - 静态方法:使用
static关键字定义的类方法 - 访问器:通过
get/set定义属性访问器 - 私有属性:使用
#符号定义的私有属性
2. 与 ES5 的差异
| 特性 | ES5 实现 | ES6 类语法 |
|---|---|---|
| 构造函数 | function Person() {} | class Person {} |
| 方法定义 | Person.prototype.say = function | class Person { say() {} } |
| 继承 | Person.prototype = Object.create(...) | class Child extends Parent {} |
| 私有属性 | 无 | #privateProperty |
三、环境准备
确保支持 ES6 的环境(如现代浏览器或 Node.js 12+)。以下代码可在浏览器控制台或 Node.js 中运行。
四、核心实现
1. 基础类定义
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name}`);
}
}
const p = new Person('Alice', 30);
p.greet(); // 输出: Hello, my name is Alice关键点分析:
constructor是类的构造函数,用于初始化实例greet是类的方法,通过this访问实例属性new Person()创建实例时自动绑定this
2. 继承与多态
class Student extends Person {
constructor(name, age, grade) {
super(name, age);
this.grade = grade;
}
greet() {
console.log(`Hello, I'm student ${this.name} in grade ${this.grade}`);
}
}
const s = new Student('Bob', 15, 10);
s.greet(); // 输出: Hello, I'm student Bob in grade 10关键点分析:
extends实现继承,super()调用父类构造函数- 方法重写实现多态,子类覆盖父类方法
- 原型链自动连接,无需手动设置
prototype
3. 静态方法与访问器
class MathUtils {
static PI = 3.14159;
static circleArea(radius) {
return MathUtils.PI * radius * radius;
}
get radius() {
return this._radius;
}
set radius(value) {
if (value < 0) throw new Error('Radius cannot be negative');
this._radius = value;
}
}
console.log(MathUtils.circleArea(5)); // 输出: 78.53975关键点分析:
static定义静态方法,无需实例化即可调用get/set定义访问器,控制属性的读写- 私有属性
_radius通过访问器实现封装
五、完整案例
1. 待办事项管理器
class TodoItem {
constructor(title, completed = false) {
this.title = title;
this.completed = completed;
this.id = Date.now(); // 唯一标识
}
toggleComplete() {
this.completed = !this.completed;
}
}
class TodoList {
constructor() {
this.items = [];
}
add(item) {
this.items.push(item);
}
get count() {
return this.items.length;
}
static fromJSON(json) {
return new TodoList(json.map(item =>
new TodoItem(item.title, item.completed)
));
}
}
// 使用示例
const list = new TodoList();
list.add(new TodoItem('Finish article'));
list.add(new TodoItem('Write code', true));
console.log(list.count); // 输出: 2
console.log(list.items[0].completed); // 输出: false
// 从 JSON 初始化
const json = [
{ title: 'Read book', completed: false },
{ title: 'Write code', completed: true }
];
const listFromJSON = TodoList.fromJSON(json);
console.log(listFromJSON.count); // 输出: 2关键点分析:
TodoItem类封装单个待办事项的逻辑TodoList类管理多个事项,提供集合操作- 静态方法
fromJSON实现数据转换 - 使用唯一 ID 避免实例冲突
六、源码解析
以 TodoList.fromJSON 方法为例:
static fromJSON(json) {
return new TodoList(json.map(item =>
new TodoItem(item.title, item.completed)
));
}static关键字定义静态方法,不依赖实例map遍历 JSON 数组,创建多个TodoItem实例new TodoList()创建新的实例,将数组传递给构造函数- 构造函数将数组赋值给
this.items属性
七、进阶使用
1. 私有属性与封装
class BankAccount {
#balance = 0; // 私有属性
deposit(amount) {
if (amount < 0) throw new Error('Negative deposit not allowed');
this.#balance += amount;
}
get balance() {
return this.#balance;
}
}
const account = new BankAccount();
account.deposit(100);
console.log(account.balance); // 输出: 100关键点分析:
#balance是私有属性,无法从外部直接访问get方法提供安全的读取方式- 私有属性提升封装性,防止外部直接修改
2. 类的扩展
class Animal {
speak() {
console.log('Animal sound');
}
}
class Dog extends Animal {
speak() {
super.speak(); // 调用父类方法
console.log('Woof!');
}
}
const dog = new Dog();
dog.speak(); // 输出: Animal sound, Woof!关键点分析:
super关键字调用父类方法- 可以同时调用父类和子类方法
- 避免完全覆盖父类方法,保持多态性
八、性能与工程实践
1. 性能优化
问题:类实例化时会创建原型链,可能导致性能损耗
优化方案:
使用
Object.create替代默认原型链const PersonPrototype = { greet() { console.log('Hello'); } }; class Person { constructor() { Object.setPrototypeOf(this, PersonPrototype); } }减少不必要的实例属性
class Counter { static count = 0; static increment() { this.count++; } }使用缓存机制
class Cache { #cache = new Map(); get(key) { return this.#cache.get(key); } set(key, value) { this.#cache.set(key, value); } }
2. 安全性考虑
风险:直接暴露属性可能导致数据污染
解决方案:
使用访问器控制属性访问
class User { get password() { throw new Error('Password cannot be accessed directly'); } }隐藏敏感数据
class Payment { #token; constructor(token) { this.#token = token; } getPaymentToken() { return this.#token; } }
九、常见问题与踩坑
1. 常见错误
错误示例:
class MyClass {
method() {
console.log(this);
}
}
const obj = new MyClass();
obj.method(); // 输出: undefined原因:在非严格模式下,this 会指向全局对象
解决方案:
class MyClass {
method = () => {
console.log(this);
}
}
const obj = new MyClass();
obj.method(); // 输出: MyClass 实例改进:使用类字段语法或绑定函数
2. 遗漏 super 关键字
错误示例:
class Child {
constructor() {
this.name = 'Alice';
}
}问题:未调用 super() 导致 this 未定义
解决方案:
class Child extends Parent {
constructor() {
super();
this.name = 'Alice';
}
}3. 错误使用箭头函数
错误示例:
class MyClass {
constructor() {
this.timer = setInterval(() => {
console.log(this);
}, 1000);
}
}问题:箭头函数绑定 this 导致无法访问实例
解决方案:
class MyClass {
constructor() {
this.timer = setInterval(() => {
console.log(this);
}, 1000);
}
}十、最佳实践
1. 使用建议
适用场景:
- 需要清晰的类结构和继承关系
- 需要封装复杂对象和逻辑
- 需要使用访问器控制属性访问
- 需要利用静态方法处理类级别的逻辑
推荐做法:
- 使用类字段语法定义方法
- 优先使用
class语法而非构造函数 - 对敏感属性使用访问器
- 使用
#定义私有属性 - 通过
static方法处理类级别的逻辑
2. 不建议使用场景
不适用场景:
- 小型脚本或简单功能
- 需要动态修改原型
- 需要大量动态属性
- 项目需要完全兼容旧浏览器
替代方案:
- 使用构造函数和原型链
- 使用模块模式
- 使用函数式编程风格
十一、总结
JavaScript 的 class 语法通过封装原型机制,提供了更直观的面向对象编程方式。理解其底层原理、合理使用场景以及避免常见陷阱是关键。在实际开发中,应根据项目需求选择合适的设计模式,同时注意性能和安全性问题。通过合理使用类、访问器、静态方法等特性,可以构建更清晰、可维护的代码结构。记住:类是工具,不是万能的,要根据具体情况选择最适合的解决方案。
评论已关闭