这里的 DefaultDict 指的是类似于 Python 中的 defaultdict 的一种类。其基本特点就是当某个属性不存在于该对象中时,该对象会自动为这个属性创建一个默认值。这个默认值是由用户在创建 DefaultDict 时指定的。
举个例子,现在需要一个对象,如果某个属性不在这个对象时,在为这个属性赋值为 0.
| 1 | const words = ['hello', 'hello', 'world', 'please', 'say', 'say', 'say'] | 
这个例子其实就是非常简单的一个统计单词数量的一个例子,如果不使用 defaultDict, 那么估计就会这么写:
| 1 | const words = ['hello', 'hello', 'world', 'please', 'say', 'say', 'say'] | 
你觉得那个更美观或实用一点呢? 这个其实见仁见智,至少前者确实带来了一些便利。
回到正题,这里开始讲怎么去实现它。
Proxy 对象
实现的方法很多,不一定必须要 Proxy 对象,但它最为 ES6 推出的一个类,有必要去尝试一下。简单的说,Proxy 可以改变对象的一些默认行为,包括增删改查。
举个例子:
| 1 | const obj = new Proxy({}, { | 
可见,Proxy 对对象属性的获取进行了一点修改。在这里 obj.foo = 1  不属于对 foo 属性的获取,而是对 foo 属性的赋值(set),所以在执行 obj.foo = 1 时,get: function (target, prop) { ... } 并没有被执行。
更多的可以参考 ECMAScript 6 入门: Proxy
实现
这里先定义个 handler,也就是对对象的属性获取进行拦截。那么这里需要思考,需要哪些参数呢?
首先一个,如何确认默认值,那么默认值的产生需要用户定义。所以我们需要一个 defaultFactory 函数用于生成默认值,这里使用了函数,为了有更多的可操作空间。
另外,如何判断一个属性在不在这个对象中呢?大部分用 'foo' in obj 判断,但极少时候用其它方式。所以这里就设置一个默认操作,如果用户没有指定,我们就用 in 操作符判断属性是否存在。
这么到这里可以基本实现了 defaultDict:
| 1 | function defaultDictFactory (initials, defaultFactory, validator) { | 
defaultDictFactory 作为一个工厂函数,专门生产 defaultDict。本来我想用 class 实现,不过遇到了瓶颈,所以改为工厂模式。initials 为初始对象,因为用户或许会将一个非空对象转化为 defaultDict。defaultFactory 函数用于生产默认值。validator 判断属性是否存在,可以有用户自定义判断属性是否存在的规则。
但为了安全起见,可以加一些对参数的检查。
| 1 | function defaultDictFactory (initials, defaultFactory, validator) { | 
这样子基本就完成了 defaultDictFactory 的定义。
Example
这里还是以统计单词为例,增加 1 个要求: 单词的默认值为单词的长度
那么只需要设置 defaultFactory:
| 1 | const words = ['hello', 'hello', 'world', 'please', 'say', 'say', 'say'] | 
其它
建立 defaultDict 的最初想法一方面来自于 Python 的 defaultdict,因为这确实挺方便的。另一方面则来自于对平时刷题时经常遇到的 obj.foo = obj.foo == null ? 1 : obj.foo + 1 的这种写法觉得不美观的写法,所以试图改变一下。
