如下是关于前端项目模块化的实践,包含如下内容:javascript
使用 Webpack 打包基础设施代码已经很大程度上解决了生产力,但日益复杂业务和逻辑仍然让前端陷入“动态一时爽、重构火葬场”的笑谈,TypeScript 为解决这个问题而来。html
在本章节咱们使用 TypeScript 完成一个相似 LINQ 中 Enumerable<T>
的实现,涉及代码编写与测试用例,仍然不深刻关于 TypeScript 的讨论。前端
假想须要实现这样一个功能:好比一组学生,咱们但愿按照根据班级分组,接着按年龄排序,最后按照名称排序。java
这并不复杂,这些步骤是有前后的,每步操做获得的都是一组对象集合,分组和排序是常规数组操做,写几个循环就能够达到目标。git
void Main() { var students = new List<Student> { new Student { Classes = "class-1", Name = "Rattz", Age = 11 }, new Student { Classes = "class-2", Name = "Rose", Age = 10 }, new Student { Classes = "class-1", Name = "Mike", Age = 11 } }; var seq = students.GroupBy(x => x.Classes) .Select(g => new { Classes = g.Key, Members = g.OrderBy(x => x.Age) .ThenBy(x => x.Name) .Select(x => x) }); } class Student { public String Classes { get; set; } public String Name { get; set; } public Int32 Age { get; set; } }
得力于静态语言的类型保证及 LINQ 语法对内存对象的操做能力,一行代码就简单而有表现力地解决了问题,在 LINQPad 里组织以下:es6
如今来看 JavaScript 的写法,使用 ES6 的 Map
对象极大地减化了代码。github
let students = [ {'classes': 'class-1', 'name': 'Rattz', 'age': 11}, {'classes': 'class-2', 'name': 'Rose', 'age': 10}, {'classes': 'class-1', 'name': 'Mike', 'age': 11}, ]; let map = new Map(); for (let item of students) { let classes = map.get(item.classes); if (Object.is(classes, undefined)) { classes = [item]; map.set(item.classes, classes); } else { classes.push(item); } } for (let [, value] of map.entries()) { value.sort((p1, p2) => { if (p1.age !== p2.age) { return p1.age - p2.age; } return p1.name.localeCompare(p2.name); }); } let groups = []; for (let [key, value] of map.entries()) { groups.push({ classes: key, members: value, }); }
reduce
很强有力但很难一次编写正确代码略多,包含了3个循环,若是不使用 Map
代码就要进行数组查找;若是想压缩循环,就使用 Array.prototype.reduce
函数,代码就很难懂了。typescript
let students = [ {'classes': 'class-1', 'name': 'Rattz', 'age': 11}, {'classes': 'class-2', 'name': 'Rose', 'age': 10}, {'classes': 'class-1', 'name': 'Mike', 'age': 11}, ]; let groups = students.reduce((previous, current) => { let arr = previous.filter(x => x.key === current.classes); if (arr.length > 0) { arr[0].members.push(current); } else { previous.push({ classes : current.classes, members: [current], }); } return previous; }, []); for(let g of groups) { g.members.sort((a, b) => a.name.localeCompare(b.name)); }
下文经过编写类库完成相似功能,咱们充分使用生产力而先不考虑语言、版本问题,看看具体的调用部分c#
interface Student { classes: string, name: string age: number } let students: Student[] = [ {'classes': 'class-1', 'name': 'Rattz', 'age': 11}, {'classes': 'class-2', 'name': 'Rose', 'age': 10}, {'classes': 'class-1', 'name': 'Mike', 'age': 11}, ]; let groups = Enumerable.from(students) .groupBy(x => x.classes) .select(x => ({ classes: x.key, members: Enumerable.from(<Student[]>x.items) .sortBy((x, y) => x.age - y.age) .thenSortBy((x, y) => x.name.localeCompare(y.name)) }));
完整代码见于 Enumerable 行 100 左右。数组
若是 Enumerable
的实现是 JavaScript 版本,这部分代码可能充满了疑问,即使阅读源码也很难一会儿理解实现者的用意
groupby
须要什么样的参数?select
使用了 groupBy
返回值,它包含怎样的数据结构?sortBy
但返回了什么?thenSortBy
如何进行二次排序,又返回了什么?TypeScript 可以解答上述问题问题,如下是函数签名。
groupBy
:完整签名是 groupBy<K, U>(keySelector: (value: T, index: number) => K, valueSelector?: (value: T, index: number) => U): Enumerable<Group<K, T | U>>
,虽然稍长可是阅读起来也就那回事
keySelector
: 接受2个参数(分别是数组元素和索引)返回1个值,一般是对象的某个属性,valueSelector
: 和keySelector
类似,表示获得新元素的方法,术语是“投影”Enumerable<Group<K, T | U>>
是 groupBy
的返回类型,表示仍然是 Enumerable<>
实例,只是内部元素由传入的 valueSelector
和keySelector
决定,该签名使得链式调用成为可能;select
: 完整签名是 select<U>(callback: (value: T, index: number) => U): Enumerable<U>
,和 groupBy
相似,但更加简单sortBy
: 和 Array.prototype.sort
功能类似,但签名是 sortBy(compareFn: (a: T, b: T) => number): OrderedEnumerable<T>
,返回 OrderedEnumerable<>
实例thenSortBy
:同 sortBy
,依赖 OrderedEnumerable<T>
的内部实现咱们能够在安全地传入参数和引用返回值,在代码编写阶段就能获得编译器的语法、值入参数合法性检查。