如何用 JavaScript 實現一個陣列惰性求值庫
在程式語言理論中,惰性求值(英語:Lazy Evaluation),又譯為惰性計算、懶惰求值,也稱為傳需求呼叫(call-by-need),是一個計算機程式設計中的一個概念,它的目的是要最小化計算機要做的工作。它有兩個相關而又有區別的含意,可以表示為“延遲求值”和“最小化求值”,除可以得到效能的提升外,惰性計算的最重要的好處是它可以構造一個無限的資料型別。
看到函式式語言裡面的惰性求值,想自己用 JavaScript 寫一個最簡實現,加深對惰性求值瞭解。用了兩種方法,都不到 80 行實現了基本的陣列的惰性求值。
怎麼實現
惰性求值每次求值的時候並不是返回數值,而是返回一個包含計算引數的求值函式,每次到了要使用值得時候,才會進行計算。
當有多個惰性操作的時候,構成一個求值函式鏈,每次求值的時候,每個求值函式都向上一個求值函式求值,返回一個值。最後當計算函式終止的時候,返回一個終止值。
具體實現
判斷求值函式終止
每次求值函式都會返回各種資料,所以得使用一個獨一無二的值來作為判斷流是否完成的標誌。剛好 Symbol() 可以建立一個新的 symbol ,它的值與其它任何值皆不相等。
const over = Symbol(); const isOver = function (_over) { return _over === over; }
生成函式 range
range 函式接受一個起始和終止引數,返回一個求值函式,執行求值函式返回一個值,終止的時候返回終止值。
const range = function (from, to) { let i = from; return function () { if (i < to) { i++ console.log('range\t', i); return i } return over; } }
轉換函式 map
接受一個求值函式和處理函式,獲取求值函式 flow 中的資料,對資料進行處理,返回一個流。
const map = function (flow, transform) { return function () { const data = flow(); console.log('map\t', data); return isOver(data) ? data : transform(data); } }
過濾函式 filter
接受一個求值函式,對求值函式 flow 中資料進行過濾,找到符合的資料並且返回。
const filter = function (flow, condition) { return function () { while(true) { const data = flow(); if (isOver(data)) { return data; } if(condition(data)) { console.log('filter\t', data); return data; } } } }
中斷函式 stop
接受一個求值函式,當達到某個條件時中斷,可以用閉包函式加上 stop 函式接著實現一個 take 函式。
const stop = function (flow, condition) { let _stop = false; return function () { if (_stop) return over; const data = flow(); if (isOver(data)) { return data; } _stop = condition(data); return data; } } const take = function(flow, num) { let i = 0; return stop(flow, (data) => { return ++i >= num; }); }
收集函式 join
因為返回的都是一個函式,最後得使用一個 join 函式來收集所有的值並且返回一個陣列。
const join = function (flow) { const array = []; while(true) { const data = flow(); if (isOver(data)) { break; } array.push(data); } return array; }
測試:
const nums = join(take(filter(map(range(0, 20), n => n * 10), n => n % 3 === 0), 2)); console.log(nums); /* 輸出 range 1 map 1 range 2 map 2 range 3 map 3 filter 30 range 4 map 4 range 5 map 5 range 6 map 6 filter 60 [ 30, 60 ] */
更優雅的實現
上面使用 函式 + 閉包 實現了惰性求值,但是還是不夠優雅,絕大部分程式碼都放到迭代和判斷求值是否完成上面去了。其實 es6 中還有更好方法來實現惰性求值,就是使用 generator,generator 已經幫我們解決了迭代和判斷流是否完成,我們就可以專注於邏輯,寫出更簡潔易懂結構清晰的程式碼。
const range = function* (from, to) { for(let i = from; i < to; i++) { console.log('range\t', i); yield i; } } const map = function* (flow, transform) { for(const data of flow) { console.log('map\t', data); yield(transform(data)); } } const filter = function* (flow, condition) { for(const data of flow) { console.log('filter\t', data); if (condition(data)) { yield data; } } } const stop = function*(flow, condition) { for(const data of flow) { yield data; if (condition(data)) { break; } } } const take = function (flow, number) { let count = 0; const _filter = function (data) { count ++ return count >= number; } return stop(flow, _filter); }
還得加上鍊式呼叫才算是完成了。
class _Lazy{ constructor() { this.iterator = null; } range(...args) { this.iterator = range(...args); return this; } map(...args) { this.iterator = map(this.iterator, ...args); return this; } filter(...args) { this.iterator = filter(this.iterator, ...args); return this; } take(...args) { this.iterator = take(this.iterator, ...args); return this; } [Symbol.iterator]() { return this.iterator; } } function lazy () { return new _Lazy(); }
最後再測試一下:
const nums = lazy().range(0, 100).map(n => n * 10).filter(n => n % 3 === 0).take(2); for(let n of nums) { console.log('num:\t', n, '\n'); } /* 輸出 range 0 map 0 filter 0 num: 0 range 1 map 1 filter 10 range 2 map 2 filter 20 range 3 map 3 filter 30 num: 30 */
好了,大功告成。
總結
這樣我們就完成了一個最簡的陣列惰性求值的庫,這裡只是簡單實現了惰性求值,要放到工程中還需要新增很多細節。因為程式碼不過 80 行,可以很清楚的瞭解惰性求值原理,還能加深對生成器的理解。
最後這裡是 github 地址。
相關文章
- 利用 Lambda 表示式實現 Java 中的惰性求值Java
- javascript實現複製一個陣列程式碼例項JavaScript陣列
- 如何用JavaScript手動實現一個棧JavaScript
- 惰性求值——lodash原始碼解讀原始碼
- JavaScript 中實現等分陣列JavaScript陣列
- javascript 偽陣列實現方法JavaScript陣列
- javascript實現的對陣列每一個元素都執行一個函式JavaScript陣列函式
- 說說如何用 JavaScript 實現一個模板引擎JavaScript
- javascript如何清空一個array陣列JavaScript陣列
- javascript如何複製一個陣列JavaScript陣列
- js如何實現拷貝一個陣列JS陣列
- javascript將陣列的元素每兩個一組存入一個新陣列JavaScript陣列
- JavaScript陣列(一)JavaScript陣列
- javascript如何實現二維陣列效果JavaScript陣列
- 【譯】如何用 ES6 去重一個陣列陣列
- JavaScript遍歷陣列每一個元素JavaScript陣列
- JavaScript刪除陣列第一個元素JavaScript陣列
- javascript實現的合併兩個陣列程式碼例項JavaScript陣列
- 第九章:type、newtype和惰性求值
- javascript實現二維陣列實現簡單介紹JavaScript陣列
- JavaScript刪除陣列最後一個元素JavaScript陣列
- JavaScript獲取陣列最後一個元素JavaScript陣列
- javascript在陣列開頭新增一個元素JavaScript陣列
- javascript擷取陣列的一個區間JavaScript陣列
- JavaScript 陣列一次追加多個元素JavaScript陣列
- JavaScript 刪除陣列最後一個元素JavaScript陣列
- 利用Lambda表示式進行Java中的惰性求值Java
- JavaScript --二維陣列查詢一維陣列JavaScript陣列
- JavaScript實現陣列去重的常見方式JavaScript陣列
- JavaScript陣列 幾個常用方法JavaScript陣列
- js實現從陣列中取出一個隨機項JS陣列隨機
- javascript如何判斷一個物件是不是陣列JavaScript物件陣列
- 將一個陣列賦值給另外一個陣列陣列賦值
- 把JavaScript標準庫之陣列一網打盡JavaScript陣列
- JavaScript二維陣列轉換成一維陣列JavaScript陣列
- 陣列1——求一個陣列的最大子陣列陣列
- JavaScript 陣列常見操作(一)JavaScript陣列
- JavaScript 陣列JavaScript陣列