这里的 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
的这种写法觉得不美观的写法,所以试图改变一下。