前端练习45 单例模式

知识点

  1. Proxy
  2. 单例模式

题目

单例模式(Singleton)是一种常用的软件设计模式,它保证我们系统中的某一个类在任何情况实例化的时候都获得同一个实例。例如:

const root1 = new Root()
const root2 = new Root()
const root3 = new Root()

root1 === root2 // true
root2 === root3 // true

我们构造一个名为singletonify方法,可以传入一个用户自定义的类,可以返回一个新的单例模式的类。例如:

class A () {}
const SingleA = singletonify(A)

const a1 = new SingleA()
const a2 = new SingleA()
const a3 = new SingleA()

a1 === a2 // => true
a2 === a3 // => true

注意,你要保证singletonify返回的类的实例也是原来的类的实例:

a1 instanceof A // => true
a1 instanceof SingleA // => true

自定义的类属性也要保持一致,例如:

class A () {}
A.staticMethod = () => {}

const SingleA = singletonify(A)
SingleA.staticMethod === A.staticMethod // => true

实现

分析并拆解题目

(1)首先要求singletonify 能够通过new生成实例,所以singletonify需要是一个构造函数

(2)singletonify生成的实例是一个单例,也就是多次调用,返回同一个对象,所以需要通过闭包实现一个保存在内存中的对象作为调用返回的单例

(3)要求返回的实例是原来类的实例,并且singletonify继承了原来的类的静态方法,所以需要使用extends来实现继承

extends来实现继承的原理是:

class A extends from B {
  constructor () {
    super ()
  }
}

// 等同于
Object.setPrototypeOf(A, B);
Object.setPrototypeOf(A.prototype, B.prototype);

Object.setPrototypeOf(A, B)的作用是A.__proto__ = B,实现了A对B的静态方法的继承

Object.setPrototypeOf(A.prototype, B.prototype)作用是A.prototype.__proto__ = B.prorotype,如果假设aA的实例,那么就实现了下面的继承链:

a.__proto__ === A
A.prototype.__proto__ === B.prorotype

(4)extendsconstructor的返回值和构造函数返回值在是Object类型时是相同的(constructor不能返回基本类型,构造函数可以但是会忽略),默认情况是返回this,我们这里要返回的就是那个单例

所以有:

const singletonify = (OriginalClass) => {
  let single = new OriginalClass();
  class SingleClass extends OriginalClass {
    constructor (...props) {
      super(...props);
      return single
    }
  }
  return SingleClass
};

(5)到这里我们所有要求都满足了,除了下面这一条:

a1 instanceof SingleA 

命名是通过new SingleA产生的实例,反而不是SingleA的实例,这就是因为我们在constuctor里面没有返回this,而是返回的A的实例

所以要对返回的结果的原型链在进行修正

(笨死了,看了别人的答案才明白的,为什么自己想不到,蠢货一个)

a1 instanceof SingleA的关系实现就是因为下面的继承链存在:

a1.__proto__ === SingleA.prototype

而我们现在的继承链是:

a1.__proto__ === A.prototype

所以需要:

Object.setPrototype(a1, SingelA.prototype)

所以目前完整的原型链是:

a1.__proto__ === SingelA.prototype; 
// a1是SingelA的实例

a1.__proto__.__proto__ === SingelA.prototype.__proto__ === A.prototype; 
//  SingelA继承了A, 所以a1是A的实例

SingleA.__proto__ === A; 
// SingleA继承了A的静态方法 

最终的结果是:

const singletonify = (OriginalClass) => {
  let single = new OriginalClass();
  class SingleClass extends OriginalClass {
    constructor (...props) {
      super(...props);
      return single
    }
  }
  Object.setPrototypeOf(single, SingleClass.prototype);
  return SingleClass
};

Proxy

翻看了评论区,发现还可以用Proxy实现

(Proxy真是太强大了,什么操作都可以拦截么)

Proxy(target, handler)中的constructor可以拦截new操作符

var p = new Proxy(target, {
  construct: function(target, argumentsList, newTarget) {
  }
});
  • target是目标对象。
  • argumentsListconstructor的参数列表。
  • newTarget是最初被调用的构造函数,就上面的例子而言是p

construct中this会绑定到handler

所以可以这样实现:

const singletonify = (OriginalClass) => {
  let single = new OriginalClass();
  return new Proxy(OriginalClass, {
    construct(target, arguments, newTarget) {
      return single
    }
  });
};

因为返回的Proxy是对原来的构造函数的拦截,所以会继承原来的构造函数的特性,而因为construct中this会绑定到handler,所以返回的实例也是singletonify的实例

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值