如下是關於前端項目模塊化的實踐,包含如下內容: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>
的內部實現咱們能夠在安全地傳入參數和引用返回值,在代碼編寫階段就能獲得編譯器的語法、值入參數合法性檢查。