Js中的class
是ES6引入的重要特性,它提供了一种更接近传统面向对象编程(OOP)的方式来定义对象和组织代码,但实际上class仍然是基于原型(prototype)的语法糖。
面向对象编程(OOP)
老生常谈的话题了,面向对象编程(Object-Oriented Programming)是一种以对象为核心的编程范式,通过将数据和操作数据的行为封装在一起,模拟现实世界中的实体和它们之间的关系。核心思想是抽象、封装、继承和多态。
核心概念
- 类(Class)
- 类是对象的抽象,而对象是类的具体实例。简单理解就是类是"模板","设计图"
- 对象(Object)
- 对象是类的实例,是属性和方法的集合
- 属性 (Properties)
- 描述对象的状态或特征
- 方法(Method)
- 描述对象的行为或功能
四大基本原则
- 抽象(Abstraction)
- 提取对象共性特征,忽略不必要的细节,形成一个简化的模型
- 题外话,在现实生活中,我认为这是一项非常重要的能力,比如那句"人是社会关系的总和"对"抽象人性论"的批判,有兴趣可以了解一下。
- 封装(Encapsulation)
- 将数据和操作数据的方法捆绑在一起,对外隐藏实现细节,仅通过接口与外界交互
- 继承(Inheritance)
- 子类可以继承父类的属性和方法,并可以扩展或覆盖父类的功能,实现代码复用
- 多态(Polymorphism)
- 不同对象对同一方法调用有不同的行为,即"同一接口,不同实现",实现灵活拓展
基于原型的继承
回到Js中,继承是实现对象间属性和方法可共享的重要机制,由于Js是基于原型的语言(不同于传统面向对象语言),其主要的继承方式主要依赖原型链和构造函数的组合使用。 这里先介绍一下基于类的语言和基于原型的语言
特性 | 基于类的语言 | 基于原型的语言 |
---|---|---|
继承机制 | 通过类定义模板,实例继承类的属性和方法 | 通过对象直接继承另一个对象的属性和方法 |
对象创建 | 类是模板,实例是类的副本 | 对象直接从其他对象(原型)继承属性 |
方法共享 | 方法定义在类中,所有实例共享 | 方法定义在原型对象上,所有继承该原型的对象共享 |
灵活性 | 类是静态的,难以动态修改 | 原型是动态的,运行时可修改原型,影响所有继承者 |
关键区别:Js中的"类"是语法糖,底层仍然是基于原型链实现
在Js中原型的基本概念
- 每个对象都有一个原型(Prototype)
- 原型链:对象通过原型链关联其他对象。当访问一个对象的属性或方法时,Js引擎会沿着原型链向上查找,直到找到匹配的属性/方法,或到达原型链的末尾(null)
原型优缺点
优点
- 代码复用
- 动态扩展
- 灵活的继承
缺点
- 引用类型共享问题
- 原型链查找性能
Js中的常见继承方式
原型链继承
原理:将子类的原型设置为父类的实例
js
function Parent() {
this.name = 'Parent'
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {}
Child.prototype = new Parent() // 将子类的原型指向父类的实例
const child = new Child()
console.log(child.getName()) // 输出: Parent
优点
- 简单易实现
- 子类可以继承父类的实例属性和原型方法
缺点
- 所有子类实例共享父类实例的引用类型属性(修改一个实例会影响其他实例)
- 创建子类实例时无法向父类构造函数传参
构造函数继承 (借用call/apply)
原理:在子类构造函数中调用父类构造函数,继承实例属性
js
function Parent(name) {
this.name = name
this.colors = ['red', 'blue']
}
Parent.prototype.getName = function() {
return this.name
}
function Child(name) {
Parent.call(this, name) // 调用父类构造函数,指定当前Child中的this;每次创建子类实例都会调用
}
const child1 = new Child('Tom')
child1.colors.push('green')
const child2 = new Child('George')
console.log(child1.colors) // ['red', 'blue', 'green']
console.log(child2.colors) // ['red', 'blue']
console.log(child1.getName()) // child1.getName is not a function
优点
- 避免引用类型属性共享问题
- 可以向父类构造函数传参
缺点
- 无法继承父类原型上的方法
- 每次创建子类实例都会重复调用父类构造函数,造成性能浪费
组合继承 (原型链 + 构造函数)
原理:结合原型链和构造函数的优点,是Js中最常用的继承模式
js
function Parent(name) {
this.name = name
this.colors = ['red','green']
}
Parent.prototype.getName = function() {
return this.name
}
function Child(name, age) {
Parent.call(this, name) // 第一次调用父类构造函数(继承实例属性)
this.age = age
}
Child.prototype = new Parent() // 第二次调用父类构造函数(继承原型方法)
Child.prototype.constructor = Child // 修复子类原型对象的 `constructor` 属性**,确保其正确指向子类的构造函数
const child1 = new Child('Tom', 18)
console.log(child1.getName()) // 输出:Tom
优点
- 解决了原型链继承和构造函数继承的缺陷
- 子类可以继承父类的实例属性和原型方法
缺点
- 父类构造函数会被调用两次
寄生继承
原理:在原型继承的基础上增强对象,类似于"寄生"模式
js
function createChild(parent) {
const child = Object.create(parent) // 原型式继承创建对象
// 添加新的方法(增强);每次调用寄生函数时,都会为对象添加新方法,可能导致多个对象拥有独立的函数副本(与原型链共享不同)
child.sayHello = function() {
console.log(`Hello, ${this.name}`)
}
return child
}
const parent = {
name: 'Parent'
}
const child = createChild(parent)
child.sayHello() // 输出:Hello,Parent
优点
- 灵活扩展对象功能
缺点
- 每个实例都会创建新方法,不共享方法
Object.create()
是Js中用于创建对象的核心方法之一,它允许你指定新对象的原型并定义其属性及其特征。它在原型继承、对象组合以及创建无原型对象的场景中非常有用
注意点: - 不复制属性:Object.create()
只建立原型链,不会复制属性。修改原型对象会影响所有继承它的实例
寄生组合式继承(最优方案)
原理:通过Object.create()
直接继承父类原型,避免调用两次父类构造函数
js
function Parent(name) {
this.name = name
this.colors = ['red','green']
}
Parent.prototype.getName = function() {
return this.name
}
function Child(name, age) {
Parent.call(this, name) // 调用父类构造函数(继承实例属性)
this.age = age
}
// 使用 Object.create 直接继承父类原型,避免调用 Patent 构造函数
// 将子类的原型指定为父类的原型
Child.prototype = Object.crate(Parent.prototype)
Child.prototype.constructor = Child
const child1 = new Child('Tom', 18)
console.log(child1.getName()) // 输出:Tom
优点
- 只调用了一次父类构造函数
- 保留了组合继承的所有优点
缺点
- 实现相对复杂
Js中的类
以上说了那么多种继承方式,现在我们来看看Js中的Class是如何使用的,其实Class语法底层就是基于寄生组合式继承的核心思想,通过更高级的语法糖封装了原型链和构造函数操作。那接下来让我们了解一下Js中Class的应用。
类的基本定义
类(Class)是ES6引入的重要特性,它提供了一种更接近传统面向对象编程的方式,简化了对象和继承的定义。类通过class
关键字定义,本质上还是基于原型的语法糖,但语法更加简洁直观
js
class Person {
// 构造函数
constructor(name, age) {
this.name = name
this.age = age
}
// 实例方法
sayHello() {
console.log(`Hello, my name is ${this.name}`)
}
// 静态方法
static sayHi() {
console.log('Hi from static method')
}
}
// 静态方法调用
Person.sayHi()
// 创建实例
const person = new Person('George',25)
// 实例方法调用
person.sayHello()
构造函数 (Constructor)
作用:用于初始化对象的属性 特点:每个类可以有一个constructor
方法,如果没有显式定义,会自动生成一个空的构造函数
js
class Animal {
constructor(name) {
this.name = name;
}
}
const cat = new Animal("Whiskers");
console.log(cat.name); // "Whiskers"
实例方法和静态方法
实例方法:通过类的实例调用,可以访问实例的属性
静态方法:直接通过类名调用,不依赖实例
继承(Inheritance)
通过extends
关键词实现继承,super
用于调用父类构造函数或方法
js
class Animal {
constructor(name) {
this.name = name
}
speak() {
console.log(`${this.name} makes a noise`)
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name)
this.breed = breed
}
speak() {
super.speak()
console.log(`${this.name} barks!`)
}
}
const dog = new Dog('dogName','dogBreed')
dog.speak()
// dogName makes a noise
// dogName barks!
私有属性和方法
通过 #
符号定义私有成员(ES2022+),只能在类内部访问
js
class BankAccount {
#balance = 0 // 私有属性
deposite(amount) {
this.#balance += amount
}
getBalance() {
return this.#balance
}
#withdraw(amount) { // 私有方法
if(amount > this.#balance) {
throw new Error("Insufficient funds")
}
this.#balance -= amount
}
}
const account = new BankAccount()
account.deposite(1000)
console.log(account.getBalance()) // 1000
account.#withraw(500) // Uncaught Error: Cannot read private member #withraw from an object whose class did not declare it
类的高级特性
Getter/Setter
用于控制属性的访问和赋值逻辑
js
class Circle {
constructor(radius) {
this._radius = radius
}
get area() {
return Math.PI * this._radius ** 2
}
set radius(value) {
if(value <= 0 ) throw new Error("Radius must be positive")
this._radius = value
}
}
const circle = new Circle(5)
console.log(circle.area)
circle.radius = 10 // 调用setter
Ts中的类
类的定义和语法
扩展了类型注解 和访问修饰符
ts
class Animal {
public name: string;
constructor(name: string) {
this.name = name;
}
speak(): void {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
override speak(): void {
console.log(`${this.name} barks!`);
}
}
类的访问控制
访问修饰符通过public
、private
、protected
、readonly
显示声明成员的访问权限
public
:公共成员(默认)private
:仅类内部可访问protected
:类内部及子类可访问readonly
:定义只读属性
ts
class BankAccount {
private balance: number = 0;
public deposit(amount: number): void {
this.balance += amount;
}
protected getBalance(): number {
return this.balance;
}
}
class SavingsAccount extends BankAccount {
getBalance(): number {
return this.getBalance(); // ✅ 允许访问 protected 方法
}
}
类的类型系统
静态类型:编译时强制类型检查 类型注解:显示声明属性和方法类型
ts
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
const user = new User(123); // ❌ 报错:参数类型不匹配
类的高级特性
接口(Interface)
定义类的结构约束
ts
interface Animal {
name: string
speak(): void
}
class Dog implements Animal {
name:string
construtor(name:string) {
this.name = name
}
speak():void {
console.log(`${this.name} barks!`);
}
}
抽象类(Abstract)
定义抽象类,强制子类实现
ts
abstract class Animal {
abstract speak(): void;
}
class Dog extends Animal {
speak(): void {
console.log("Bark!");
}
}
多态(Polymorphism)
ts
class Animal {
speak(): void {
console.log("Animal sound");
}
}
class Dog extends Animal {
speak(): void {
console.log("Bark!");
}
}
function makeSound(animal: Animal) {
animal.speak();
}
makeSound(new Animal()); // "Animal sound"
makeSound(new Dog()); // "Bark!"
装饰器(Decorator)
装饰器是Ts的一种元编程特性,通过@
符号定义,用于修改类或类成员的行为。如果你使用过nest的话对这个会很熟悉
类装饰器
作用:修改类的构造函数或者添加元数据
ts
function sealed(constructor: Function) {
// `Object.seal(obj)` 是 JavaScript 用于密封对象的静态方法
Object.seal(constructor)
Object.seal(constructor.prototype)
}
@sealed
class Person {
name:string
constructor(name:string) {
this.name = name
}
}
方法装饰器
作用:修改方法的行为(如日志记录,权限校验)
ts
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling method "${key}" with arguments ${JSON.stringify(args)}`);
return originalMethod.apply(this, args);
};
return descriptor;
}
class MathUtils {
@log
add(a: number, b: number): number {
return a + b;
}
}
const utils = new MathUtils();
utils.add(2, 3); // 输出调用日志
属性装饰器
作用:修改属性的访问器或添加元数据
ts
function format(target: any, key: string) {
let value = target[key];
const getter = () => value;
const setter = (newVal: string) => {
value = newVal.toUpperCase();
};
Object.defineProperty(target, key, { get: getter, set: setter });
}
class User {
@format
name: string;
constructor(name: string) {
this.name = name;
}
}
const user = new User("alice");
console.log(user.name); // "ALICE"
总结对比表
特性 | JavaScript | TypeScript |
---|---|---|
类定义 | class 关键字 |
class + 类型注解 |
访问控制 | 默认 public ,# 表示私有 |
public /private /protected/readonly 显式声明 |
装饰器 | 实验性支持(需编译器) | 原生支持 @ 修饰符 |
类型系统 | 动态类型 | 静态类型 + 类型推断 |
接口 | 无 | 支持 interface |
抽象类 | 手动实现 | 支持 abstract |
工具支持 | 基础智能提示 | 强大的 IDE 支持(自动补全、类型检查) |
编译与运行 | 直接运行 | 需编译为 JavaScript |
适用场景 | 小型项目、快速开发 | 大型项目、团队协作 |
总结
JavaScript 的 class
是基于原型的语法糖,TypeScript 的 class
是其强类型增强版
- 了解一下面向对象(OOP)的概念
- 掌握 JavaScript 原型继承 → 理解
class
底层机制。 - 学习JavaScript类 -> 理解
class
的使用 - 再学习 TypeScript 类 → 利用类型系统提升开发效率。
- 最后实践装饰器/抽象类 → 深化 OOP 设计能力。