知识点
- Proxy
- 单例模式
题目
单例模式(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
,如果假设a
是A
的实例,那么就实现了下面的继承链:
a.__proto__ === A
A.prototype.__proto__ === B.prorotype
(4)extends
的constructor
的返回值和构造函数返回值在是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
是目标对象。argumentsList
是constructor
的参数列表。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
的实例