从JS到TS:类、构造函数与单例模式,一篇吃透
从JS到TS:类、构造函数与单例模式,一篇吃透

作为一名前端开发者,从熟悉的JavaScript切换到TypeScript项目时,遇到类、构造函数、单例这些概念经常会感到困惑这太正常了。
其实这些概念本质上是JavaScript已有特性的“强类型升级版”,只是TypeScript给它们加了更严谨的约束。这篇文章我会用最通俗的语言,结合大量JS/TS对比示例,帮你彻底搞懂这些核心概念。
一、先搞懂:TS中的类(Class)到底是什么?
1. 类的本质:JS的语法糖,TS的“强类型模板”
在ES6之前,JS没有真正的“类”,我们靠构造函数模拟面向对象;
ES6引入class关键字,本质是构造函数的语法糖;
而TS的class则是在ES6类的基础上,增加了类型约束,让代码更严谨。
简单说:类是创建对象的“模板”,就像做饼干的模具,用同一个模具能做出规格一致的饼干(对象),TS的类还会强制要求饼干的“配料”(属性/方法)必须符合预设类型。
2. TS类的基础语法(对比JS)
先看一个最基础的例子,帮你快速对比差异:
JS中的类
// JS类:无类型约束,属性可以随便加
class Person {
// 构造函数:创建对象时初始化属性
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法
sayHi() {
console.log(`我是${this.name},今年${this.age}岁`);
}
}
// 创建实例
const zhangsan = new Person('张三', 20);
zhangsan.sayHi(); // 我是张三,今年20岁
zhangsan.gender = '男'; // 随便加属性,JS不限制
TS中的类
// TS类:必须先声明属性类型,构造函数参数也要约束
class Person {
// 第一步:声明实例属性 + 类型(TS特有)
name: string;
age: number;
// 可选属性:加?表示可以不存在
gender?: string;
// 第二步:构造函数(参数也要加类型)
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// 实例方法(返回值也可以加类型)
sayHi(): void {
console.log(`我是${this.name},今年${this.age}岁`);
}
// 带返回值的方法
getAge(): number {
return this.age;
}
}
// 创建实例:参数类型不对会直接报错(TS的核心价值)
const zhangsan = new Person('张三', 20); // 正确
// const lisi = new Person(20, '李四'); // 报错:类型不匹配
zhangsan.sayHi(); // 我是张三,今年20岁
// zhangsan.gender = '男'; // 可选属性可以赋值
// zhangsan.height = 180; // 报错:未声明的属性不能加
3. TS类的核心特性(比JS多的约束)
(1)访问修饰符(TS特有)
用来控制类的属性/方法能被谁访问,这是TS类最常用的特性,也是你看项目代码时最常见的:
| 修饰符 | 含义 | 示例 |
|---|---|---|
public(默认) | 公开的,类内、子类、外部都能访问 | public name: string; |
private | 私有的,只有当前类内部能访问 | private age: number; |
protected | 受保护的,类内、子类能访问,外部不能 | protected gender: string; |
示例:
class Person {
public name: string; // 公开
private age: number; // 私有
protected gender: string; // 受保护
constructor(name: string, age: number, gender: string) {
this.name = name;
this.age = age;
this.gender = gender;
}
private getAge(): number { // 私有方法
return this.age;
}
public showInfo(): void {
// 类内部可以访问所有属性/方法
console.log(`姓名:${this.name},年龄:${this.getAge()},性别:${this.gender}`);
}
}
const zhangsan = new Person('张三', 20, '男');
console.log(zhangsan.name); // 正常:public可以访问
// console.log(zhangsan.age); // 报错:private不能外部访问
// console.log(zhangsan.gender); // 报错:protected不能外部访问
zhangsan.showInfo(); // 正常:通过public方法间接访问私有/受保护属性
// 子类示例
class Student extends Person {
constructor(name: string, age: number, gender: string) {
super(name, age, gender);
}
showGender(): void {
console.log(this.gender); // 正常:子类能访问protected
// console.log(this.age); // 报错:子类不能访问private
}
}
(2)只读属性(readonly)
TS特有,用readonly修饰的属性只能在构造函数中赋值,后续不能修改:
class Person {
readonly id: number; // 只读属性
name: string;
constructor(id: number, name: string) {
this.id = id; // 构造函数中可以赋值
this.name = name;
}
}
const zhangsan = new Person(1, '张三');
zhangsan.name = '张三三'; // 正常:普通属性可修改
// zhangsan.id = 2; // 报错:readonly属性不能修改
二、构造函数(constructor):类的“初始化函数”
1. 构造函数的核心作用
构造函数是类中特殊的方法,只有两个核心功能:
- 当用
new创建类的实例时,自动执行; - 给实例的属性初始化赋值。
你可以把构造函数理解为:“用模具做饼干时,往模具里放配料的步骤”,每次做饼干(创建实例)都会执行这一步,保证饼干有基础配料。
2. TS构造函数的简化写法(项目中常用)
TS支持在构造函数参数上直接加修饰符/类型,省略“声明属性+赋值”的步骤,这是项目代码中最常见的写法,一定要掌握:
简化前
class Person {
private name: string;
public age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
简化后(项目中常用)
class Person {
// 直接在参数上加修饰符,自动声明为实例属性并赋值
constructor(
private name: string,
public age: number
) {}
}
const zhangsan = new Person('张三', 20);
console.log(zhangsan.age); // 正常:public
// console.log(zhangsan.name); // 报错:private
3. 构造函数的继承(super关键字)
当子类继承父类时,子类的构造函数必须先调用super()(父类的构造函数),否则TS会报错:
class Person {
constructor(public name: string) {}
}
class Student extends Person {
constructor(name: string, public score: number) {
super(name); // 必须先调用父类构造函数,传递参数
this.score = score;
}
}
const student = new Student('李四', 90);
console.log(student.name, student.score); // 李四 90
三、单例模式:让类“只能创建一个实例”
1. 单例模式的核心场景
为什么项目中会用单例?比如:
- 全局的弹窗管理器:整个项目只能有一个,避免重复创建;
- 全局的状态管理器:保证数据唯一;
- 工具类:比如日期工具、请求工具,只需要一个实例即可。
简单说:单例模式就是强制一个类只能创建出一个实例对象,后续无论怎么调用,都返回同一个实例。
2. TS实现单例模式的核心思路
实现单例的关键有3步:
- 把构造函数设为
private:外部不能用new创建实例; - 在类内部创建一个静态的“实例缓存”;
- 提供一个静态方法(比如
getInstance()):检查缓存,有则返回,无则创建。
3. 基础版单例(项目中最常用)
class ModalManager {
// 1. 静态属性:缓存唯一实例(static表示属于类,而非实例)
private static instance: ModalManager;
// 2. 私有构造函数:外部不能new
private constructor() {
console.log('创建弹窗管理器实例');
}
// 3. 静态方法:获取唯一实例
public static getInstance(): ModalManager {
// 检查缓存:没有则创建,有则直接返回
if (!ModalManager.instance) {
ModalManager.instance = new ModalManager();
}
return ModalManager.instance;
}
// 实例方法:比如打开弹窗
openModal(content: string): void {
console.log(`打开弹窗:${content}`);
}
}
// 测试:无论调用多少次,只创建一次实例
const modal1 = ModalManager.getInstance();
const modal2 = ModalManager.getInstance();
console.log(modal1 === modal2); // true:是同一个实例
modal1.openModal('提示信息'); // 打开弹窗:提示信息
modal2.openModal('错误信息'); // 打开弹窗:错误信息
// const modal3 = new ModalManager(); // 报错:构造函数是private,不能外部调用
4. 带初始化参数的单例(进阶版)
如果单例需要初始化参数(比如请求工具需要baseURL),可以这样写:
class RequestUtil {
private static instance: RequestUtil;
private baseURL: string;
// 私有构造函数,接收初始化参数
private constructor(baseURL: string) {
this.baseURL = baseURL;
}
// 静态方法,支持传参初始化
public static getInstance(baseURL: string = 'https://api.default.com'): RequestUtil {
if (!RequestUtil.instance) {
RequestUtil.instance = new RequestUtil(baseURL);
}
return RequestUtil.instance;
}
// 实例方法:发送请求
get(path: string): string {
return `${this.baseURL}${path}`;
}
}
// 第一次调用传参,初始化baseURL
const request1 = RequestUtil.getInstance('https://api.xxx.com');
console.log(request1.get('/user')); // https://api.xxx.com/user
// 后续调用不传参,复用已有实例
const request2 = RequestUtil.getInstance();
console.log(request2.get('/order')); // https://api.xxx.com/order
console.log(request1 === request2); // true
四、实战:TS类+单例的完整示例(项目级)
结合上面的知识点,写一个项目中常见的“本地存储工具类(单例)”,帮你串联所有知识点:
/**
* 本地存储工具类(单例模式)
* 功能:封装localStorage,增加类型约束和异常处理
*/
class StorageUtil {
// 单例缓存
private static instance: StorageUtil;
// 前缀:区分不同项目的存储key
private prefix: string;
// 私有构造函数:初始化前缀
private constructor(prefix: string = 'app_') {
this.prefix = prefix;
// 检查localStorage是否可用(比如浏览器隐私模式)
this.checkStorageAvailable();
}
// 获取单例实例
public static getInstance(prefix?: string): StorageUtil {
if (!StorageUtil.instance) {
StorageUtil.instance = new StorageUtil(prefix);
}
return StorageUtil.instance;
}
// 私有方法:检查localStorage可用性
private checkStorageAvailable(): void {
try {
const testKey = `${this.prefix}test`;
localStorage.setItem(testKey, 'test');
localStorage.removeItem(testKey);
} catch (e) {
throw new Error('本地存储不可用,请关闭浏览器隐私模式!');
}
}
// 公开方法:设置存储(支持任意类型,自动JSON.stringify)
public set<T>(key: string, value: T): void {
const fullKey = `${this.prefix}${key}`;
const strValue = JSON.stringify(value);
localStorage.setItem(fullKey, strValue);
}
// 公开方法:获取存储(支持泛型,自动JSON.parse)
public get<T>(key: string): T | null {
const fullKey = `${this.prefix}${key}`;
const strValue = localStorage.getItem(fullKey);
if (!strValue) return null;
try {
return JSON.parse(strValue) as T;
} catch (e) {
console.error('解析存储数据失败', e);
return null;
}
}
// 公开方法:删除存储
public remove(key: string): void {
const fullKey = `${this.prefix}${key}`;
localStorage.removeItem(fullKey);
}
// 公开方法:清空当前项目的所有存储
public clear(): void {
const keys = Object.keys(localStorage);
keys.forEach(key => {
if (key.startsWith(this.prefix)) {
localStorage.removeItem(key);
}
});
}
}
// 使用示例
const storage = StorageUtil.getInstance('my_project_');
// 存储数据(TS会校验类型)
storage.set<string>('username', '张三');
storage.set<number>('age', 20);
storage.set<{ id: number; name: string }>('user', { id: 1, name: '张三' });
// 获取数据(指定泛型,返回对应类型)
const username = storage.get<string>('username'); // string | null
const user = storage.get<{ id: number; name: string }>('user'); // {id: number; name: string} | null
console.log(username); // 张三
console.log(user?.id); // 1
// 复用实例
const storage2 = StorageUtil.getInstance();
console.log(storage === storage2); // true
总结
- TS类:是JS类的“强类型版”,核心增加了属性/方法的类型约束、访问修饰符(public/private/protected)、只读属性,本质还是创建对象的模板;
- 构造函数:类的初始化方法,
new实例时自动执行,TS支持参数简化写法,子类必须通过super()调用父类构造函数; - 单例模式:核心是
private构造函数 + 静态缓存实例 + 静态获取方法,保证类只有一个实例,常用于全局工具类、管理器。
记住:TS的类和单例都是在JS基础上增加了类型约束,核心逻辑和JS是一致的,先理解“为什么用”,再记“怎么写”,很快就能上手项目中的TS代码了。
更多推荐

所有评论(0)