JavaScript和TypeScript的单例写法(面向对象)

本文介绍了一种适用于H5游戏开发的单例模式实现方式,包括TypeScript和JavaScript两种语言的具体实现,并提供了实例验证其正确性和有效性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、单例类的作用

在我们平时开发H5游戏过程中,有某别对象,希望在内存中只有一份实例,其他任何地方想要获取到这个实例,只能通过这个类提供的静态方法来获取到实例,而任何地方进行new来进行构造的话,都会报错。总结一下这个单例类的要求

  1. 这个类只允许进行一个new的调用构造函数行为
  2. 提供静态方法来访问唯一实例
  3. 提供重复new会报错的设计
  4. 具备合理优化的设计模式

二、单例设计模式

后面是借鉴的AS3的经典写法,写出的JavaScript和TypeScript版本的单例类。
这里的Singleton其实是基类的来的,子类只要继承它就自然能够获得唯一实例的判断了。相当的方便。
实际生产环境的单例基类的内部类封装:

/** 存放初始化过的构造函数 **/
private static classMap: Dictionary<any, Object> = new Dictionary();
constructor()
{
    var clazz: any = clazz = ClassUtils.forInstance(this);
    //为空时,表示浏览器不支持这样读取构造函数
    if (!clazz)
        return;
    // 防止重复实例化
    if (Singleton.classMap.hasKey(clazz))
        throw new Error(this + " 只允许实例化一次!");
    else
        Singleton.classMap.put(clazz, this);
}

可以看到把key和value用一个Dictionary对象来封装了。不过在这里就不搞太复杂了。直接给出最完整的代码。

设计原理是通过一个所有实例都共享的一个数组来存放已经初始化过的对象,然后在那个对象的构造函数里检测这个对象是否已经实例话过了。所以在这里我们就采用2个数组来存放。

/** 存放初始化过的构造函数,这里用数组来存放构造函数 **/
private static classKeys:Function[] = [];
private static classValues:any[] = [];

这里设计得比较面向对象,比较适合开发Html5游戏了。

一、TypeScript的单例写法

Singleton.ts

/**
 * 所有单例的基类,做了单例的基础检查。所有子类最好都写一个getInstance的静态方法来获取
 * @author sodaChen
 * Date:2012-10-29
 */
class Singleton
{
    //其实实际的开发项目中,不一定会用到数组,有可能会把数组之类的进行封装
    /** 存放初始化过的构造函数,这里用数组来存放构造函数 **/
    private static classKeys:Function[] = [];
    private static classValues:any[] = [];

    constructor()
    {
        var clazz: any = this["constructor"];
        //为空时,表示浏览器不支持这样读取构造函数
        if (!clazz)
            return;
        // 防止重复实例化
        if (Singleton.classKeys.indexOf(clazz) != -1)
            throw new Error(this + " 只允许实例化一次!");
        else
        {
            Singleton.classKeys.push(clazz);
            Singleton.classValues.push(this);
        }

    }
    //注意,Singleton是要替换成你自己实现的子类 这里没有实际的作用
    private static instance:Singleton;
    /**
     * 获取实例的静态方法实例
     * @return
     *
     */
    public static getInstance():Singleton
    {
        if(!this.instance)
        {
            this.instance = new Singleton();
        }
        return this.instance;
    }
    /**
     * 销毁方法。事实上单例是很少进行销毁的
     */
    destroy(o: any = null): void
    {
        this.onDestroy();
        Singleton.removeInstance(this["constructor"]);
    }

    /**
     * 子类重写的方法
     */
    protected onDestroy(): void
    {

    }
    /**
     * 删除单例的实例(不对单例本身做任何的销毁,只是删除他的引用)
     * @param clazz 单例的Class对象
     *
     */
    static removeInstance(clazz: Function): void
    {
        var index: number = this.classKeys.indexOf(clazz);
        if (index == -1)
        {
            return null;
        }
        this.classKeys.splice(index, 1);
        this.classValues.splice(index, 1);
    }

    /**
     * 是否存放有这个构造函数
     * @param clazz 构造函数
     * @return {boolean}
     */
    static getFunValue(clazz: Function):any
    {
        let funs:Function[] = this.classKeys;
        let length:number = funs.length;
        for(let i:number = 0; i < length; i++)
        {
            if(clazz == funs[i])
                return this.classValues[i];
        }
        return null;
    }

    /**
     * 获取单例类,若不存在则创建.所有的单例创建的时候,都必须使用这个方法来创建,这样可以做到统一管理单例
     * @param clazz 任意需要实现单例效果的类
     * @return
     *
     */
    static getInstanceOrCreate(clazz:any): any
    {
        var obj: any = this.getFunValue(clazz);
        if (obj)
        {
            return obj;
        }
        obj = new clazz();
        //不是Singleton的子类,则手动添加Singleton构造器会自动添加到classMap
        if (!(obj instanceof Singleton))
        {
            this.classKeys.push(clazz);
            this.classValues.push(obj);
        }
        return obj;
    }
}

测试代码:

var singleton1 = Singleton.getInstance();
console.log("通过Singleton.getInstance实例:" + singleton1);
var singleton2 = Singleton.getInstance();
console.log("singleton1 == singleton2:" + (singleton1 == singleton2));
new Singleton();

测试输出结果:

通过Singleton.getInstance实例:[object Object]
singleton1 == singleton2:true
Uncaught Error: [object Object] 只允许实例化一次!
  Singleton 
  main  
  onload

实际使用中,可以直接继承Singleton,再增加一个静态方法就可以了,比如TickMgr.ts

private static instance:TickMgr;
/**
 * 获取实例的静态方法实例
 * @return
 *
 */
public static getInstance():TickMgr
{
    if(!this.instance)
    {
        this.instance = new TickMgr();
    }
    return this.instance;
}

二、JavaScript的单例写法

原理是一样的,因为TypeScript本身就是编译成js代码的。

/**
 * 所有单例的基类,做了单例的基础检查。所有子类最好都写一个getInstance的静态方法来获取
 * @author sodaChen
 * Date:2012-10-29
 */
var Singleton = (function () {
    function Singleton() {
        var clazz = this["constructor"];
        //为空时,表示浏览器不支持这样读取构造函数
        if (!clazz)
            return;
        // 防止重复实例化
        if (Singleton.classKeys.indexOf(clazz) != -1)
            throw new Error(this + " 只允许实例化一次!");
        else {
            Singleton.classKeys.push(clazz);
            Singleton.classValues.push(this);
        }
    }
    /**
     * 获取实例的静态方法实例
     * @return
     *
     */
    Singleton.getInstance = function () {
        if (!this.instance) {
            this.instance = new Singleton();
        }
        return this.instance;
    };
    /**
     * 销毁方法。事实上单例是很少进行销毁的
     */
    Singleton.prototype.destroy = function (o) {
        if (o === void 0) { o = null; }
        this.onDestroy();
        Singleton.removeInstance(this["constructor"]);
    };
    /**
     * 子类重写的方法
     */
    Singleton.prototype.onDestroy = function () {
    };
    /**
     * 删除单例的实例(不对单例本身做任何的销毁,只是删除他的引用)
     * @param clazz 单例的Class对象
     *
     */
    Singleton.removeInstance = function (clazz) {
        var index = this.classKeys.indexOf(clazz);
        if (index == -1) {
            return null;
        }
        this.classKeys.splice(index, 1);
        this.classValues.splice(index, 1);
    };
    /**
     * 是否存放有这个构造函数
     * @param clazz 构造函数
     * @return {boolean}
     */
    Singleton.getFunValue = function (clazz) {
        var funs = this.classKeys;
        var length = funs.length;
        for (var i = 0; i < length; i++) {
            if (clazz == funs[i])
                return this.classValues[i];
        }
        return null;
    };
    /**
     * 获取单例类,若不存在则创建.所有的单例创建的时候,都必须使用这个方法来创建,这样可以做到统一管理单例
     * @param clazz 任意需要实现单例效果的类
     * @return
     *
     */
    Singleton.getInstanceOrCreate = function (clazz) {
        var obj = this.getFunValue(clazz);
        if (obj) {
            return obj;
        }
        obj = new clazz();
        //不是Singleton的子类,则手动添加Singleton构造器会自动添加到classMap
        if (!(obj instanceof Singleton)) {
            this.classKeys.push(clazz);
            this.classValues.push(obj);
        }
        return obj;
    };
    return Singleton;
}());
//其实实际的开发项目中,不一定会用到数组,有可能会把数组之类的进行封装
/** 存放初始化过的构造函数,这里用数组来存放构造函数 **/
Singleton.classKeys = [];
Singleton.classValues = [];

测试代码是一样的。

三、JavaScript更简单和比较传统的单例写法

这个写法其实就是比较偏向做js前端,而不是h5游戏的。
一般是创建一个命名空间来存放,其他地方使用就不会有new的写法了
比如

var asf = {};  

asf.singleton = {} ;  
asf.TickMgr = {};  

外面则是直接调用

asf.singleton.pay("mc");
asf.TickMgr.tick();
<think>我们正在讨论TypeScript中类的私有属性(private)的使用方法最佳实践。 根据引用[2]引用[4]的内容,TypeScript在类的基础上增加了访问控制,其中private是访问修饰符之一。 私有属性(private): 1. 使用private关键字声明的属性或方法只能在类内部访问,不能在类的外部访问(包括子类)。 2. 这有助于封装,确保类的内部状态不被外部直接修改,只能通过类提供的方法来操作。 最佳实践: 1. 将不需要在类外部访问的属性方法设置为私有,以隐藏实现细节。 2. 通过公共方法(public)来提供对私有属性的安全访问修改,可以在方法中添加验证逻辑。 3. 注意:TypeScript的私有属性只是在编译阶段进行约束,在编译成JavaScript后,私有属性实际上变成了普通的属性(在ES2015之前,甚至没有真正的私有属性,而是通过闭包模拟)。从ES2015开始,JavaScript也有了自己的私有字段(使用#前缀),TypeScript 3.8以后也支持这种语法。 两种私有属性的写法: a. 使用private关键字(TypeScript传统写法): class MyClass { private myPrivateProp: string; constructor() { this.myPrivateProp = "private"; } } b. 使用ECMAScript的私有字段(以#开头,是真正的运行时私有): class MyClass { #myPrivateProp: string; constructor() { this.#myPrivateProp = "private"; } } 比较: 传统private:编译后属性名不变,只是编译器检查时不允许外部访问。在运行时,通过实仍然可以访问(但违反了设计意图)。 #私有字段:是JavaScript标准的私有字段,编译后属性名会被改写,运行时外部无法访问。 建议: 如果项目运行环境支持ES2015及以上,并且希望私有属性在运行时也真正私有,推荐使用#语法。 如果项目需要兼容旧环境,或者不介意运行时可以访问(只通过编译阶段约束),可以使用private关键字。 示代码(使用private关键字): class Person { private name: string; constructor(name: string) { this.name = name; } public getName(): string { return this.name; } public setName(newName: string): void { if (newName.length > 0) { this.name = newName; } } } // 使用 const person = new Person("Alice"); console.log(person.getName()); // 正确:通过公共方法访问 // console.log(person.name); // 错误:编译时报错,因为name是私有属性 示代码(使用#私有字段): class Person { #name: string; constructor(name: string) { this.#name = name; } public getName(): string { return this.#name; } public setName(newName: string): void { if (newName.length > 0) { this.#name = newName; } } } 注意:在子类中,私有属性(无论是private还是#)都是不可访问的。 引用[4]中有一个子: class Control { private state: any; // 私有属性 } // 接口SelectableControl继承自Control,要求实现select方法 interface SelectableControl extends Control { select(): void; } // Button类继承Control并实现SelectableControl接口 class Button extends Control implements SelectableControl { select() { /* 实现逻辑 */ } } 在这个子中,Button类继承了Control的私有属性state,但是Button类内部不能直接访问state(因为state是Control的私有属性,只能在Control内部访问)。Button类只能通过Control类提供的公共方法(如果有的话)来操作state。 总结: 1. 私有属性用于隐藏内部状态,提供更好的封装性。 2. TypeScript提供两种方式:传统的private(编译时私有)#字段(运行时私有)。 3. 最佳实践是使用私有属性配合公共方法来实现对属性的安全访问修改。 相关问题: 1. TypeScript中privateprotected修饰符有什么区别? 2. 如何在TypeScript中实现单例模式(利用私有构造函数)? 3. 在TypeScript中,如何通过类方法暴露私有属性的值而不暴露引用(避免外部修改)?</think>### TypeScript 类中 `private` 关键字的使用方法及最佳实践 在 TypeScript 中,`private` 是访问修饰符的关键字,用于实现类的封装性,确保类的内部状态不被外部直接访问。以下是详细指南: --- #### 一、基本用法 ```typescript class BankAccount { private balance: number; // 私有属性 constructor(initialBalance: number) { this.balance = initialBalance; } public deposit(amount: number): void { if (amount > 0) { this.balance += amount; // 类内部可访问 } } public getBalance(): number { return this.balance; // 通过公共方法暴露 } } const account = new BankAccount(100); account.deposit(50); console.log(account.getBalance()); // ✅ 150 // console.log(account.balance); // ❌ 编译错误:Property 'balance' is private ``` --- #### 二、关键特性 1. **编译时约束** - `private` 是 TypeScript 的**类型系统特性**,编译后会移除 - 编译后代码:`balance` 变成普通 JavaScript 属性(无真正私有性) 2. **运行时真正私有方案**(ES2022+) ```typescript class SecureAccount { #pin: string; // 使用 # 语法(运行时真正私有) constructor(pin: string) { this.#pin = pin; } } ``` 3. **继承限制** 子类无法访问父类的私有成员: ```typescript class SavingsAccount extends BankAccount { attemptAccess() { // console.log(this.balance); ❌ 编译错误 } } ``` --- #### 三、最佳实践 1. **优先用于内部状态管理** ```typescript class User { private passwordHash: string; // 敏感数据设为私有 setPassword(password: string) { this.passwordHash = this.hashPassword(password); } } ``` 2. **配合公共方法暴露数据** 通过 getter 控制访问: ```typescript class TemperatureSensor { private currentTemp: number = 0; public get temperature(): number { return this.round(this.currentTemp); // 添加处理逻辑 } private round(value: number): number { return Math.round(value * 100) / 100; } } ``` 3. **避免直接返回引用类型** 防止外部修改私有数组/对象: ```typescript class ShoppingCart { private items: string[] = []; // ✅ 安全:返回副本 public getItems(): string[] { return [...this.items]; } } ``` 4. **接口与私有属性的兼容性** 接口不能定义私有成员,但可实现含私有成员的类: ```typescript interface Selectable { select(): void; } class Button implements Selectable { private state = "inactive"; // ✅ 允许 select() { this.state = "active"; } } ``` --- #### 四、与 `protected` 的对比 | 特性 | `private` | `protected` | |--------------|----------------------------|----------------------------| | 类内部访问 | ✅ | ✅ | | 子类访问 | ❌ | ✅ | | 实访问 | ❌ | ❌ | | 典型应用场景 | 完全内部状态 | 需要子类扩展的共享状态 | > 当需要子类继承时使用 `protected`,否则优先用 `private`[^2][^4]。 --- #### 五、设计模式应用 **单例模式**(利用私有构造函数): ```typescript class AppConfig { private static instance: AppConfig; private config: object = {}; private constructor() {} // 阻止外部实化 public static getInstance(): AppConfig { if (!this.instance) { this.instance = new AppConfig(); } return this.instance; } } const config = AppConfig.getInstance(); // ✅ 唯一实 // const config2 = new AppConfig(); ❌ 编译错误 ``` --- **总结**: 1. 使用 `private` 封装内部实现细节,降低耦合度 2. 通过公共方法提供受控访问 3. 敏感数据优先使用 `#` 语法实现运行时私有 4. 避免在接口中定义私有成员要求 > 私有属性是 TypeScript 实现面向对象**封装性**的核心机制,能显著提升代码健壮性[^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值