如何用JavaScript實(shí)現(xiàn)一個(gè)數(shù)組惰性求值庫
概述
在編程語言理論中,惰性求值(英語:Lazy Evaluation),又譯為惰性計(jì)算、懶惰求值,也稱為傳需求調(diào)用(call-by-need),是一個(gè)計(jì)算機(jī)編程中的一個(gè)概念,它的目的是要最小化計(jì)算機(jī)要做的工作。它有兩個(gè)相關(guān)而又有區(qū)別的含意,可以表示為“延遲求值”和“最小化求值”,除可以得到性能的提升外,惰性計(jì)算的最重要的好處是它可以構(gòu)造一個(gè)無限的數(shù)據(jù)類型。
看到函數(shù)式語言里面的惰性求值,想自己用JavaScript寫一個(gè)最簡實(shí)現(xiàn),加深對惰性求值了解。用了兩種方法,都不到 80 行實(shí)現(xiàn)了基本的數(shù)組的惰性求值。
怎么實(shí)現(xiàn)
惰性求值每次求值的時(shí)候并不是返回?cái)?shù)值,而是返回一個(gè)包含計(jì)算參數(shù)的求值函數(shù),每次到了要使用值得時(shí)候,才會(huì)進(jìn)行計(jì)算。
當(dāng)有多個(gè)惰性操作的時(shí)候,構(gòu)成一個(gè)求值函數(shù)鏈,每次求值的時(shí)候,每個(gè)求值函數(shù)都向上一個(gè)求值函數(shù)求值,返回一個(gè)值。最后當(dāng)計(jì)算函數(shù)終止的時(shí)候,返回一個(gè)終止值。
具體實(shí)現(xiàn)
判斷求值函數(shù)終止
每次求值函數(shù)都會(huì)返回各種數(shù)據(jù),所以得使用一個(gè)獨(dú)一無二的值來作為判斷流是否完成的標(biāo)志。剛好 Symbol() 可以創(chuàng)建一個(gè)新的 symbol ,它的值與其它任何值皆不相等。
const over = Symbol(); const isOver = function (_over) { return _over === over; }
生成函數(shù) range
range 函數(shù)接受一個(gè)起始和終止參數(shù),返回一個(gè)求值函數(shù),運(yùn)行求值函數(shù)返回一個(gè)值,終止的時(shí)候返回終止值。
const range = function (from, to) { let i = from; return function () { if (i < to) { i++ console.log('range\t', i); return i } return over; } }
轉(zhuǎn)換函數(shù) map
接受一個(gè)求值函數(shù)和處理函數(shù),獲取求值函數(shù) flow 中的數(shù)據(jù),對數(shù)據(jù)進(jìn)行處理,返回一個(gè)流。
const map = function (flow, transform) { return function () { const data = flow(); console.log('map\t', data); return isOver(data) ? data : transform(data); } }
過濾函數(shù) filter
接受一個(gè)求值函數(shù),對求值函數(shù) flow 中數(shù)據(jù)進(jìn)行過濾,找到符合的數(shù)據(jù)并且返回。
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; } } } }
中斷函數(shù) stop
接受一個(gè)求值函數(shù),當(dāng)達(dá)到某個(gè)條件時(shí)中斷,可以用閉包函數(shù)加上 stop 函數(shù)接著實(shí)現(xiàn)一個(gè) take 函數(shù)。
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; }); }
收集函數(shù) join
因?yàn)榉祷氐亩际且粋€(gè)函數(shù),最后得使用一個(gè) join 函數(shù)來收集所有的值并且返回一個(gè)數(shù)組。
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
更優(yōu)雅的實(shí)現(xiàn)
上面使用 函數(shù) + 閉包 實(shí)現(xiàn)了惰性求值,但是還是不夠優(yōu)雅,絕大部分代碼都放到迭代和判斷求值是否完成上面去了。其實(shí) es6 中還有更好方法來實(shí)現(xiàn)惰性求值,就是使用 generator,generator 已經(jīng)幫我們解決了迭代和判斷流是否完成,我們就可以專注于邏輯,寫出更簡潔易懂結(jié)構(gòu)清晰的代碼。
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); }
還得加上鏈?zhǔn)秸{(diào)用才算是完成了。
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
好了,大功告成。
總結(jié)
這樣我們就完成了一個(gè)最簡的數(shù)組惰性求值的庫,這里只是簡單實(shí)現(xiàn)了惰性求值,要放到工程中還需要添加很多細(xì)節(jié)。因?yàn)榇a不過 80 行,可以很清楚的了解惰性求值原理,還能加深對生成器的理解。
以上就是如何用JavaScript實(shí)現(xiàn)一個(gè)數(shù)組惰性求值庫的詳細(xì)內(nèi)容,更多關(guān)于JavaScript實(shí)現(xiàn)數(shù)組惰性求值庫的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
wufengteam?core統(tǒng)一中心注冊器功能解析
這篇文章主要為大家介紹了wufengteam?core統(tǒng)一中心注冊器功能解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11p5.js碼繪“跳動(dòng)的小正方形”的實(shí)現(xiàn)代碼
這篇文章主要介紹了p5.js碼繪“跳動(dòng)的小正方形”,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10淺析JavaScript中break、continue和return的區(qū)別
這篇文章主要介紹了JavaScript中break、continue和return的區(qū)別,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-11-11JS?中數(shù)組的增刪改查和對象的增刪改查實(shí)例詳解
這篇文章主要介紹了JS?中數(shù)組的增刪改查和對象的增刪改查實(shí)例詳解,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07javascript實(shí)現(xiàn)一個(gè)簡單的彈出窗
本文給大家分享的是使用javascript實(shí)現(xiàn)的一個(gè)簡單的彈出窗的代碼,非常的簡單實(shí)用,有需要的小伙伴可以參考下2016-02-02JavaScript最全公共方法匯總并解析(前端開發(fā)收藏必備)
JavaScript掌握各種常用的公共方法更是提升開發(fā)效率和代碼質(zhì)量的關(guān)鍵,無論你是初學(xué)者還是資深開發(fā)者,了解并熟練運(yùn)用這些方法都能讓你的代碼更加簡潔、高效,本篇博客將為你詳細(xì)匯總并解析最全的JavaScript公共方法,涵蓋數(shù)組、對象、字符串、日期等各個(gè)方面的常用技巧2024-06-06