@marvintau/jpl
v0.1.4
Published
| 语句覆盖率 | 分支覆盖率 | 函数覆盖率 | 代码行覆盖率 | | -----------|----------|-----------|-------| | ![Statements](https://img.shields.io/badge/Coverage-76.91%25-red.svg) | ![Branches](https://img.shields.io/badge/Coverage-67.26%25-red.svg) | ![Functions](https://img.shiel
Downloads
5
Readme
| 语句覆盖率 | 分支覆盖率 | 函数覆盖率 | 代码行覆盖率 | | -----------|----------|-----------|-------| | | | | |
Chuā - 欻
简介
「欻」是一个对JavaScript内建的数据结构(Array和Plain Object)进行操作的一套函数集合。在大部分场合下我们的数据比较简单,没有很复杂的结构和行为,就像JSON表示的那样。我们不需要建立一个像LokiJS那样的内存数据库,也不需要像immutable.js那样用HAMT实现的persistent数据结构。我们只需要使用JavaScript自带的Object``表示记录,以及
Array`来表示列表就可以了。
安装
$ npm i --save @marvintau/chua
记录 (Record)
在Python中有Dict类型,在JS中对应的类型是万能的Object。然而JS中Object有太多广泛的含义,它可以是一个存放数据的字典,也可以是一个Prototype,也可以是用一个Prototype创建出来的类(prototype和类的概念还有很大区别),也可以是用一个类创建出的对象,所以我们需要加以区分。我们只想利用Plain Object来存放数据。
所谓的"Plain Object"是指通过Object Literal或Object.create({})创建的Object实例。
所谓的「记录」可以是任意Plain Object,但是以下方法会向它添加一些属性。
- __children: 是一个Array,其中将会保存作为当前
Record
孩子的其它Record
。树形结构就是靠它实现的。__children只有在执行向Record
添加孩子的时候才会创建。 - __path: 是一个Array,保存从最外层Array通向当前
Record
所经过的所有Record
的children的索引。我们可能需要单独引用Record
时可能会涉及到对它所在的原始数据结构进行操作。这时就可以通过__path
来找到对应的Record
,具体的方法会在下面的API中介绍。
设计数据结构的思路
Q: 为什么不继承Object创建一个新的Record类?
因为在JS中继承的概念比较费解。从面向对象的角度来看JS中所有的类都继承自Object,更具体地来说,就是扩展了Object.prototype。然而作为prototype的对象本身就是一个Object,这导致了对Object类的继承无论如何都会修改Object本身的定义,这也是一般的面向对象思路在Object这里都会变得很奇怪。因此你会发现很多针对Object的方法都是静态方法,例如entries
。所以为了避免麻烦,我们既不定义一个Record类来扩展Object,也不创建一个Record类并将Object作为其中一个属性,而是提供了一系列操作Object的函数,来实现我们想要的功能。
Q: 为什么__children
和__path
都是enumerable的?将它们定义为non-enumerable并使得它们不出现在for .. in
循环中难道不更好吗?
这样是会好一些,因为在逻辑上__children
和__path
并不应该作为「记录」的一部分。然而将它们设置为non-enumerable会带来一些问题,最主要的问题是序列化。大部分前后端通信的库都封装了JSON的stringify/parse的方法,使得发送方可以直接传入对象,而接收方得到的则是经过parse的对象。然而问题是non-enumerable的属性不会被序列化,因此默认情况下接收方会丢失__children
和__path
的信息,除非我们在发送之前将它们重新定义为enumerable。显然这带来了更多的麻烦和需要考量的因素。
get
用法:
get(array, {path, indexColumn, withList})
其中
array
: 包含所有数据的数组path
: 从根结点(array)到所要寻找的数据的路径。- 当path是一个array of integer时,将通过每一级
record.__children
的index来寻找。 - 当path是一个array of string时,则通过每一级
record.__children
中,某一字段匹配path中的值来寻找。此时需要给定indexColumn,也就是字段名,否则会报错。
- 当path是一个array of integer时,将通过每一级
indexColumn
: 当path是array of string时所提供的作为索引的字段名。withList
: 如果withList
的值为true
,则返回值中将包含list
,详见返回值的说明
返回:
{record, sublings, list}
其中
record
: 要找的那个Record
sublings
: 要找的Record
所在的__childrenlist
: 如果withList
参数值为true
,那么list
中将包含从array
到record
之间所有经过的record
在实现类似「自动完成」或者「提示」一类的功能时,你可能需要知道你要找的Record
的其它同辈记录,sublings就是为了完成这个目的添加进来的。
add
add(array, recs, {path, column, adIndex})
其中:
recs
: 待插入的Record
,或者包含Record
的Array
array
: 包含所有数据的数组path
: 从根结点(array)到所要寻找的数据的路径。- 当path是一个array of integer时,将通过每一级record.__children的index来寻找。
- 当path是一个array of string时,则通过每一级record.__children中,某一字段匹配path中的值来寻找。此时需要给定column,也就是字段名,否则会报错。
column
: 当path是array of string时所提供的字段名。atIndex
: 添加记录的位置。如果atIndex是一个整数,则按照Array.splice
的第一个参数向array
或记录的__children
添加记录,否则按Array.push
向后添加。
返回: 无
add
和get
的参数很类似,因为add
本身就用到了get
。如果path
指向了一个Record
,那么会向这个Record
的__children
添加。如果path
为空,或者没有第三个options的选项,那么会直接添加到array中。
示例:
const array = [{num: 0, name: 'asd'}, {num: 1, name:'dsda'}];
add(array, {num:2, name:'bsd'});
console.log(array);
const array = [{num: 0, name: 'asd'}, {num: 1, name:'dsda'}];
add(array, {num:2, name:'bsd'}, {path=[0]});
console.log(array);
set
set(array, kvs, {path, column, adIndex})
其中:
array
: 包含被操作数据的数组kvs
: 包含