关于Js和Ts中类(class)的知识

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!`);
  }
}

类的访问控制

访问修饰符通过publicprivateprotectedreadonly显示声明成员的访问权限

  • 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 是其强类型增强版

  1. 了解一下面向对象(OOP)的概念
  2. 掌握 JavaScript 原型继承 → 理解 class 底层机制。
  3. 学习JavaScript类 -> 理解class的使用
  4. 再学习 TypeScript 类 → 利用类型系统提升开发效率。
  5. 最后实践装饰器/抽象类 → 深化 OOP 设计能力。
相关推荐
前端Hardy5 小时前
只用2行CSS实现响应式布局,比媒体查询更优雅的布局方案
javascript·css·html
小菜全6 小时前
uniapp基础组件概述
前端·css·vue.js·elementui·css3
小天呐6 小时前
qiankun 微前端接入实战
前端·js·微前端
周航宇JoeZhou6 小时前
JP4-7-MyLesson后台前端(五)
java·前端·vue·elementplus·前端项目·mylesson·管理平台
车口6 小时前
滚动加载更多内容的通用解决方案
javascript
Yaavi6 小时前
一个基于markdown的高性能博客模板
前端·开源·源码
艾小码6 小时前
手把手教你实现一个EventEmitter,彻底告别复杂事件管理!
前端·javascript·node.js
幸福摩天轮6 小时前
npm发布包
前端
前端AK君6 小时前
Gitlab 线上合并冲突的坑
前端