han's bolg - 年糕記

面试系列:从一道单例题讲起

引子

弱鸡刷scriptoj的时候,看到这么一道单例试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
单例模式(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
请你完成 singletonify 的编写。

我就稀里糊涂的写了这么个答案,居然通过了!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const singletonify = (OriginalClass) => {
let singleton = null
function init() {
const sl = new OriginalClass()
sl.__proto__ = OriginalClass.prototype
return sl
}
const myConstrutor = function () {
if (!singleton) {
singleton = init()
}
return singleton
}
myConstrutor.prototype = OriginalClass.prototype
Object.getOwnPropertyNames(OriginalClass).forEach((val, idx, array) => {
myConstrutor[val] = OriginalClass[val]
})
return myConstrutor
}

但翻看答案的时候,发现考察的点确是Proxy,这块的知识点没点上,那就看看MDN上是咋写的。

Proxy

Proxy,顾名思义是个代理,代理干啥呢,代理给target添加新的功能handler,又不用修改target本身。
按照阮一峰老师在ES6中的解释,proxy就相当于java web中的拦截器,在正式使用target之前赋予其一些handler的功能。

语法

1
let p = new Proxy(target, handler);

参数

  • target
    用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
  • handler
    一个对象,其属性是当执行一个操作时定义代理的行为的函数。

看一下MDN上这个示例吧,扩展构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function extend(sup,base) {
var descriptor = Object.getOwnPropertyDescriptor(
base.prototype,"constructor"
);
base.prototype = Object.create(sup.prototype);
var handler = {
construct: function(target, args) {
var obj = Object.create(base.prototype);
this.apply(target,obj,args);
return obj;
},
apply: function(target, that, args) {
sup.apply(that,args);
base.apply(that,args);
}
};
var proxy = new Proxy(base,handler);
descriptor.value = proxy;
Object.defineProperty(base.prototype, "constructor", descriptor);
return proxy;
}

var Person = function(name){
this.name = name
};

var Boy = extend(Person, function(name, age) {
this.age = age;
});

Boy.prototype.sex = "M";

var Peter = new Boy("Peter", 13);
console.log(Peter.sex); // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age); // 13

好多不太熟悉的函数😓,我们来了解一下吧

  • Object​.getOwnPropertyDescriptor
    返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)属性描述符是一个类似这样的对象:
1
2
3
4
5
6
7
8
{
configurable: true,
enumerable: true,
get: /*the getter function*/,
set: undefined,
value: 42,
writable: true
}
  • Object​.create()
    创建一个新对象,使用现有的对象来提供新创建的对象的proto

  • Object​.defineProperty
    会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

说实话,为什么要这么搞,还是不太懂。。。(继续学习啊)

所以,用proxy来实现单例吧

1
2
3
4
5
6
7
8
9
10
11
const singletonify = (OriginalClass) => {
// 实例化class对象
let singleton = new OriginalClass()
// 返回值是OrialClass,但是添加了一个功能,当new的时候,返回同一个对象singleton
// construct方法用于拦截new命令
return new Proxy(OriginalClass, {
construct (target, args, newTarget) {
return singleton
}
})
}

参考文献

  1. http://es6.ruanyifeng.com/#docs/proxy
  2. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy