js中的单例模式

本次分享的内容是设计模式中的单例模式。本文介绍了什么是单例模式,单例模式的两种实现方式和各种的优缺点,最后介绍了如何借助ES6新特性Proxy实现包装一个单例模式。

单例模式

单例模式就是保证一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式的优点:

  1. 由于单例模式只生成一个实例,所以能减少系统性能的开销;

  2. 避免对共享资源的多重占用导致的性能损耗,如日志文件,应用配置。

  3. 提供了对唯一实例的受控访问,加快对象访问速度,比如多线程线程池的设计,方便对池中的线程进行控制。

单例模式的特点:

  1. 保证一个类中只有一个实例;

  2. 在类中实例化自己;

  3. 向整个系统提供这个实例;

单例模式的实现

单例模式被大家熟知的有两种实现方式:饿汉式单例模式和懒汉式单例模式。

两种实现方式的核心要点相同就是构造函数私有化,然后将一个静态私有变量确立为唯一实例,外部通过静态方法访问这个唯一的实例。

-懒汉式单例模式

class Singleton1{
    /*核-一个接收实例的静态成员*/
    private static people:Singleton1;
    private name:string;
    /*核心-私有构造函数*/
    private constructor(name:string) {
        this.name = name;
    }
    /*核心-获取实例*/
    public static getInstance():Singleton1{
        if(Singleton1.people == null){
            Singleton1.people = new Singleton1("懒汉式单例模式");
        }
        return Singleton1.people;
    }
    public say():void{
        console.log(`${this.name}`);
    }
}
/*测试代码*/
let people = Singleton1.getInstance();
people.say(); //懒汉式单例模式
class Singleton1{
    /*核-一个接收实例的静态成员*/
    private static people:Singleton1;
    private name:string;
    /*核心-私有构造函数*/
    private constructor(name:string) {
        this.name = name;
    }
    /*核心-获取实例*/
    public static getInstance():Singleton1{
        if(Singleton1.people == null){
            Singleton1.people = new Singleton1("懒汉式单例模式");
        }
        return Singleton1.people;
    }
    public say():void{
        console.log(`${this.name}`);
    }
}
/*测试代码*/
let people = Singleton1.getInstance();
people.say(); //懒汉式单例模式

懒汉式单例模式在第一次调用时才会实例化对象,之后不会再实例化,直接返回第一次创建的对象。该模式资源利用率高,而且有延时加载的优势。但是调用效率低。

-饿汉式单例模式

class Singleton2{
    private static people:Singleton2 = new Singleton2('饿汉式单例模式');
    private name:string;
    private constructor(name) {
        this.name = name;
    }
    public static getInstance():Singleton2{
        return Singleton2.people;
    }
    public say():void{\
        console.log(this.name);
    }
}
/*测试代码*/
let s = Singleton2.getInstance();
s.say(); //饿汉式单例模式
class Singleton2{
    private static people:Singleton2 = new Singleton2('饿汉式单例模式');
    private name:string;
    private constructor(name) {
        this.name = name;
    }
    public static getInstance():Singleton2{
        return Singleton2.people;
    }
    public say():void{\
        console.log(this.name);
    }
}
/*测试代码*/
let s = Singleton2.getInstance();
s.say(); //饿汉式单例模式

饿汉式单例模式类加载时立即实例化,之后返回实例化的对象。该模式调用效率高。因为加载时立刻实例化,所以没有延时加载的优势,并且没有用到这个实例的话,就会白白实例化一个对象,浪费资源。

Proxy实现单例模式

Proxy
是ES6语法新特性,用于修改对象某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对于外部的访问进行过滤和修改。利用该特性,我们可以将一个非单例模式的类包装成为单例模式。

function singletonIfy(className):ProxyConstructor{
    return new Proxy(className.prototype.constructor,{
        construct(target: ProxyConstructor, argArray: any, newTarget?: any): object {
            if(!this.instance){
                // @ts-ignore
                this.instance = new target(...argArray);
            }
            return this.instance;
        }
    })
}

class MyClass{
    private readonly msg: string;
    constructor(msg) {
        this.msg = msg;
    }
    printMsg():void{
        console.log(this.msg);
    }
}

let MySingletonClass;
MySingletonClass = singletonIfy(MyClass);
const obj1 = new MySingletonClass('first');
obj1.printMsg(); //first
const obj2 = new MySingletonClass('second');
obj2.printMsg(); //first
function singletonIfy(className):ProxyConstructor{
    return new Proxy(className.prototype.constructor,{
        construct(target: ProxyConstructor, argArray: any, newTarget?: any): object {
            if(!this.instance){
                // @ts-ignore
                this.instance = new target(...argArray);
            }
            return this.instance;
        }
    })
}

class MyClass{
    private readonly msg: string;
    constructor(msg) {
        this.msg = msg;
    }
    printMsg():void{
        console.log(this.msg);
    }
}

let MySingletonClass;
MySingletonClass = singletonIfy(MyClass);
const obj1 = new MySingletonClass('first');
obj1.printMsg(); //first
const obj2 = new MySingletonClass('second');
obj2.printMsg(); //first

代理模式实现逻辑跟懒汉式单例模式相同,都是在调用时进行实例的初始化,之后的调用返回实例化的对象。

小结

本次分享使用TS介绍了单例模式的3种实现方式。

function singleClass (className){
    let ins 
    return new Proxy(className, {
        construct(target, args) {
            if(!ins){
                ins = new target(...args)
            }
            return ins
        }
    })
}
class BVideo {
    constructor(){
        console.log('create')
    }
}
const Video = singleClass(BVideo)
Video.prototype.play = function(){
    console.log('play')
}
const v1 = new Video()
const v2 = new Video()
v1.play()
console.log('vvvvv', v1===v2)
function singleClass (className){
    let ins 
    return new Proxy(className, {
        construct(target, args) {
            if(!ins){
                ins = new target(...args)
            }
            return ins
        }
    })
}
class BVideo {
    constructor(){
        console.log('create')
    }
}
const Video = singleClass(BVideo)
Video.prototype.play = function(){
    console.log('play')
}
const v1 = new Video()
const v2 = new Video()
v1.play()
console.log('vvvvv', v1===v2)
export type Constructor<T> = new (...arg: any[]) => T;

export function createSingleton<T extends object>(Cls: Constructor<T>): Constructor<T> {
  let instance: T;
  const handler: ProxyHandler<Constructor<T>> = {
    construct(target, args: any[], newTarget: ProxyConstructor) {
      if (!instance) {
        instance = Reflect.construct(target, args, newTarget);
      }
      return instance;
    },
  };
  return new Proxy(Cls, handler);
}
export type Constructor<T> = new (...arg: any[]) => T;

export function createSingleton<T extends object>(Cls: Constructor<T>): Constructor<T> {
  let instance: T;
  const handler: ProxyHandler<Constructor<T>> = {
    construct(target, args: any[], newTarget: ProxyConstructor) {
      if (!instance) {
        instance = Reflect.construct(target, args, newTarget);
      }
      return instance;
    },
  };
  return new Proxy(Cls, handler);
}