一切爲了抽象
一段代碼越抽象它的複用價值就越高,舉一個極端例子:javascript
function emptyAction(x) { return x }
上面這個函數就像數學裏面的 f(x) = x 同樣,與山河同在,與日月同輝!java
好比可使用它進行數組的複製:git
let a = [1, 2, 3] let aDuplicate = a.map(emptyAction)
好比能夠玩我返回我本身,雖然我也不知道這有什麼用:github
let one = emptyAction(emptyAction)(emptyAction)(1)
因此,爲了避免把那些 shiiiiit 代碼再寫一遍一遍又一遍,就抽象吧!編程
遍歷高桌子,遍歷低板凳
假設任何一組數據都永遠使用 Array 存儲,那麼下面這段代碼也是一段複用價值極高的代碼:設計模式
for (let i = 0; i < a.length; i++) { const element = a[i]; // do something }
儘管它並不直觀,我纔不想管 i 是什麼 length 是什麼,以及 daaaaamn a[i] 又是什麼!我只想遍歷數組中的每個元素,給我數組中的元素,OK???數組
並且事實上咱們除了 Array,還有 Set、Map、LinkedList 以及 maaaaany kinds of Object,很不幸它們並不支持這樣的寫法,即便實現了這樣的寫法,最終性能也會 shiiiiit 同樣(試想對一個 LinkedList 訪問第 i 個元素重複 n 次?)編程語言
來看看遍歷的一組數據的抽象描述,是這樣的:函數
const chocolates = anyObj.getChocolates() // 給我迭代器 while (chocolates.hasNext()) { const choco = chocolates.giveMeNext() // eat it }
這個 chocolates 被人們稱做巧克力迭代器、巧克力枚舉器、巧克力生成器(誤),它在 20 世紀末被做爲一種設計模式提出,旨在讓每個類型按照本身內部組織數據的特色編寫具體的遍歷方法,調用方不須要知道具體的操做,只須要喊:「給我迭代器」,而後迭代就完事。性能
編程語言語法級別的支持
各家語言的發明者都嚐到了這種設計模式的香甜滋味,紛紛在發明語言之初,或者發佈語言的最新版本時爲這種設計模式提供了語法級別的支持!而且爲標準庫中各類對象(Array、Set、Map 等)實現了迭代器!Ohhhhh yeah!!!
JavaScript 爲這種設計模式提供的語法級支持包含:
- 使用迭代協議的循環語句 for...of
- 生成器函數的一系列語法 function* () { ...; yield xxx; ... yield* fff(); ... }
迭代協議
如下內容參考文檔:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols
先看一下 for...of 的實現原理
for (const element of anyObj) { // do something }
上述代碼的等價代碼以下,看上去是稍微複雜了一些(否則爲何要提供 for...of 的語法呢)
{ // 新開一個做用域,生成的迭代器外部代碼不可見 const chocolates = anyObj[Symbol.iterator]() // 返回一個全新的迭代器 for (let chocoDesc = chocolates.next(); // 迭代出一個「巧克力描述」 !chocoDesc.done; // 不是最後一塊巧克力 chocoDesc = chocolates.next()) { const choco = chocoDesc.value // 得到巧克力 // do something } }
迭代協議能夠描述爲:
- 一個對象擁有 Symbol.iterator 方法,且該方法返回——
-
- 一個擁有 next 方法的對象,且 next 方法返回——
-
-
- 一個擁有如下兩個屬性的對象——
-
-
-
-
- done 爲表示迭代是否完成的布爾值
-
-
-
-
-
- value 爲要遍歷的元素
-
-
實現了迭代協議的對象被稱做可迭代對象,任何一個可迭代對象均可以使用 for...of 完成遍歷操做。
如下是 LeetCode 中常見的鏈表類型實現迭代協議的例子:
function ListNode(val, next = null) { this.val = val this.next = next } ListNode.prototype[Symbol.iterator] = function () { return { p: this, next: function () { if (this.p !== null) { const val = this.p.val this.p = this.p.next return { value: val, done: false } } return { value: undefined, done: true } } } }
生成器函數
暈了嗎,暈了就使用生成器函數吧!
生成器函數以 function* 標識,經過 yield 語句返回巧克力,函數執行完成就表示遍歷結束。
一樣以 LeetCode 中定義的鏈表類型爲例,迭代器是這樣實現的:
ListNode.prototype[Symbol.iterator] = function* () { let p = this while (p !== null) { yield p.val p = p.next } }
另外生成器函數的返回值自己又是一個可迭代對象(擁有 Symbol.iterator 方法……且……且),因此生成器的返回值既能夠做爲某個對象的迭代器,也能夠直接拿來迭代。
效果展現
let xs = null xs = [1, 2, 3] for (const x of xs) { console.log(x) } xs = new ListNode(1, new ListNode(2, new ListNode(3))) for (const x of xs) { console.log(x) } xs = new Set(); xs.add(1); xs.add(2); xs.add(3); for (const x of xs) { console.log(x) }
僅當使用生成器函數實現迭代器時,也能夠這樣寫
for (const x of xs[Symbol.iterator]()) { console.log(x) }
登峯造極的 LINQ
結合一切爲了抽象的理念和迭代器設計模式的成功案例,一個偉大的願望在心中誕生:
若是不止遍歷,任何數據集的任何操做都如此直觀,咱們就能夠專心的考慮業務邏輯了!
LINQ 作到了!以迭代器的設計模式爲基礎,作到了!
咱們能夠經過一連串簡單的方法調用實現對數據集的一系列操做,以下直觀的代碼簡直像說人話同樣:
Enumerable.from(iterable).where(isNeeded).select(x => x.someProperty).distinct() // iterable 是某個實現了迭代協議的可迭代對象 // Enumerable.form 將可迭代對象轉換爲了 LINQ 使用的枚舉對象 // 若是未來有一天 JavaScript 實現了官方的 LINQ 以上代碼有望寫成 iterable.where.select.distinct....
這個 LINQ 的實現位於 https://github.com/mihaifm/linq