├── package.json ├── lazy_func.js ├── lazy.js └── README.md /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lazy", 3 | "version": "0.0.1", 4 | "description": "lazy", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npm run test" 8 | }, 9 | "keywords": [ 10 | "lazy" 11 | ], 12 | "author": "jinhai.wang", 13 | "license": "MIT" 14 | } 15 | -------------------------------------------------------------------------------- /lazy_func.js: -------------------------------------------------------------------------------- 1 | const over = Symbol(); 2 | 3 | const isOver = function (_over) { 4 | return _over === over; 5 | } 6 | 7 | const range = function (from, to) { 8 | let i = from; 9 | return function () { 10 | if (i < to) { 11 | i++ 12 | console.log('range\t', i); 13 | return i 14 | } 15 | return over; 16 | } 17 | } 18 | 19 | const map = function (flow, transform) { 20 | return function () { 21 | const data = flow(); 22 | console.log('map\t', data); 23 | return isOver(data) ? data : transform(data); 24 | } 25 | } 26 | 27 | const filter = function (flow, condition) { 28 | return function () { 29 | while(true) { 30 | const data = flow(); 31 | if (isOver(data)) { 32 | return data; 33 | } 34 | if(condition(data)) { 35 | console.log('filter\t', data); 36 | return data; 37 | } 38 | } 39 | } 40 | } 41 | 42 | const stop = function (flow, condition) { 43 | let _stop = false; 44 | return function () { 45 | if (_stop) return over; 46 | const data = flow(); 47 | if (isOver(data)) { 48 | return data; 49 | } 50 | _stop = condition(data); 51 | return data; 52 | } 53 | } 54 | 55 | const take = function(flow, num) { 56 | let i = 0; 57 | return stop(flow, (data) => { 58 | return ++i >= num; 59 | }); 60 | } 61 | 62 | const join = function (flow) { 63 | const array = []; 64 | while(true) { 65 | const data = flow(); 66 | if (isOver(data)) { 67 | break; 68 | } 69 | array.push(data); 70 | } 71 | return array; 72 | } 73 | 74 | const nums = join(take(filter(map(range(0, 20), n => n * 10), n => n % 3 === 0), 2)); 75 | console.log(nums); 76 | -------------------------------------------------------------------------------- /lazy.js: -------------------------------------------------------------------------------- 1 | 2 | const range = function* (from, to) { 3 | for(let i = from; i < to; i++) { 4 | console.log('range\t', i); 5 | yield i; 6 | } 7 | } 8 | 9 | const map = function* (flow, transform) { 10 | for(const data of flow) { 11 | console.log('map\t', data); 12 | yield(transform(data)); 13 | } 14 | } 15 | 16 | const filter = function* (flow, condition) { 17 | for(const data of flow) { 18 | console.log('filter\t', data); 19 | if (condition(data)) { 20 | yield data; 21 | } 22 | } 23 | } 24 | 25 | const stop = function*(flow, condition) { 26 | for(const data of flow) { 27 | yield data; 28 | if (condition(data)) { 29 | break; 30 | } 31 | } 32 | } 33 | 34 | const take = function (flow, number) { 35 | let count = 0; 36 | const _filter = function (data) { 37 | count ++ 38 | return count >= number; 39 | } 40 | return stop(flow, _filter); 41 | } 42 | 43 | // for(const n of take(filter(map(range(0, 10000), n => n * 10), n => n % 3 === 0), 2)) { 44 | // console.log('num:\t', n, '\n'); 45 | // } 46 | 47 | class _Lazy{ 48 | constructor() { 49 | this.iterator = null; 50 | } 51 | 52 | range(...args) { 53 | this.iterator = range(...args); 54 | return this; 55 | } 56 | 57 | map(...args) { 58 | this.iterator = map(this.iterator, ...args); 59 | return this; 60 | } 61 | 62 | filter(...args) { 63 | this.iterator = filter(this.iterator, ...args); 64 | return this; 65 | } 66 | 67 | take(...args) { 68 | this.iterator = take(this.iterator, ...args); 69 | return this; 70 | } 71 | 72 | [Symbol.iterator]() { 73 | return this.iterator; 74 | } 75 | 76 | } 77 | 78 | function lazy () { 79 | return new _Lazy(); 80 | } 81 | 82 | const nums = lazy().range(0, 100).map(n => n * 10).filter(n => n % 3 === 0).take(2); 83 | 84 | for(let n of nums) { 85 | console.log('num:\t', n, '\n'); 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | ### 简介 4 | 5 | > 在编程语言理论中,惰性求值(英语:Lazy Evaluation),又译为惰性计算、懒惰求值,也称为传需求调用(call-by-need),是一个计算机编程中的一个概念,它的目的是要最小化计算机要做的工作。它有两个相关而又有区别的含意,可以表示为“延迟求值”和“最小化求值”,除可以得到性能的提升外,惰性计算的最重要的好处是它可以构造一个无限的数据类型。 6 | 7 | 看到函数式语言里面的惰性求值,想自己用 JavaScript 写一个最简实现。 8 | 用了两种方法,都不到80行实现了基本的数组的惰性求值。 9 | 10 | ### 怎么实现 11 | 12 | 惰性求值每次求值的时候并不是返回数值,而是返回一个包含计算参数的求值函数,每次到了要使用值得时候,才会进行计算。 13 | 14 | 当有多个惰性操作的时候,构成一个求值函数链,每次求值的时候,每个求值函数都向上一个求值函数求值,返回一个值。最后当计算函数终止的时候,返回一个终止值。 15 | 16 | ### 具体实现 17 | 18 | * 判断求值函数终止 19 | 20 | 每次求值函数都会返回各种数据,所以得使用一个独一无二的值来作为判断流是否完成的标志。刚好 Symbol() 可以创建一个新的 symbol ,它的值与其它任何值皆不相等。 21 | 22 | ```js 23 | const over = Symbol(); 24 | 25 | const isOver = function (_over) { 26 | return _over === over; 27 | } 28 | ``` 29 | 30 | * 生成函数 range 31 | 32 | range 函数接受一个起始和终止参数,返回一个求值函数,运行求值函数返回一个值,终止的时候返回终止值。 33 | 34 | ```js 35 | const range = function (from, to) { 36 | let i = from; 37 | return function () { 38 | if (i < to) { 39 | i++ 40 | console.log('range\t', i); 41 | return i 42 | } 43 | return over; 44 | } 45 | } 46 | ``` 47 | 48 | * 转换函数 map 49 | 50 | 接受一个流和处理函数,获取求值函数 flow 中的数据,对数据进行处理,返回一个流。 51 | 52 | ```js 53 | const map = function (flow, transform) { 54 | return function () { 55 | const data = flow(); 56 | console.log('map\t', data); 57 | return isOver(data) ? data : transform(data); 58 | } 59 | } 60 | ``` 61 | 62 | * 过滤函数 filter 63 | 64 | 接受一个流,对求值函数 flow 中数据进行过滤,找到符合的数据并且返回。 65 | 66 | ```js 67 | const filter = function (flow, condition) { 68 | return function () { 69 | while(true) { 70 | const data = flow(); 71 | if (isOver(data)) { 72 | return data; 73 | } 74 | if(condition(data)) { 75 | console.log('filter\t', data); 76 | return data; 77 | } 78 | } 79 | } 80 | } 81 | ``` 82 | 83 | * 中断函数 stop 84 | 85 | 接受一个流,当流达到某个条件时中断流,返回一个流。可以用闭包函数加上 stop 函数接着实现一个 take 函数。 86 | 87 | ```js 88 | const stop = function (flow, condition) { 89 | let _stop = false; 90 | return function () { 91 | if (_stop) return over; 92 | const data = flow(); 93 | if (isOver(data)) { 94 | return data; 95 | } 96 | _stop = condition(data); 97 | return data; 98 | } 99 | } 100 | 101 | const take = function(flow, num) { 102 | let i = 0; 103 | return stop(flow, (data) => { 104 | return ++i >= num; 105 | }); 106 | } 107 | ``` 108 | 109 | * 收集函数 join 110 | 111 | 因为返回的都是一个函数,最后得使用一个 join 函数来收集所有的值并且返回一个数组。 112 | 113 | ```js 114 | const join = function (flow) { 115 | const array = []; 116 | while(true) { 117 | const data = flow(); 118 | if (isOver(data)) { 119 | break; 120 | } 121 | array.push(data); 122 | } 123 | return array; 124 | } 125 | ``` 126 | 127 | 最后再测试一下。 128 | 129 | ```js 130 | const nums = join(take(filter(map(range(0, 20), n => n * 10), n => n % 3 === 0), 2)); 131 | console.log(nums); 132 | 133 | /* 输出 134 | range 1 135 | map 1 136 | range 2 137 | map 2 138 | range 3 139 | map 3 140 | filter 30 141 | 142 | range 4 143 | map 4 144 | range 5 145 | map 5 146 | range 6 147 | map 6 148 | filter 60 149 | 150 | [ 30, 60 ] 151 | */ 152 | ``` 153 | 154 | 大功告成。 155 | 156 | ### 更优雅的实现。 157 | 158 | 上面使用 函数 + 闭包 实现了惰性求值,但是还是不够优雅,绝大部分代码都放到迭代和判断流是否完成上面去了。其实 es6 中还有更好方法来实现惰性求值,就是 generator,generator 已经帮我们解决了迭代和判断流是否完成,我们就可以专注于逻辑,写出更简洁易懂结构清晰的代码。 159 | 160 | ```js 161 | 162 | const range = function* (from, to) { 163 | for(let i = from; i < to; i++) { 164 | console.log('range\t', i); 165 | yield i; 166 | } 167 | } 168 | 169 | const map = function* (flow, transform) { 170 | for(const data of flow) { 171 | console.log('map\t', data); 172 | yield(transform(data)); 173 | } 174 | } 175 | 176 | const filter = function* (flow, condition) { 177 | for(const data of flow) { 178 | console.log('filter\t', data); 179 | if (condition(data)) { 180 | yield data; 181 | } 182 | } 183 | } 184 | 185 | const stop = function*(flow, condition) { 186 | for(const data of flow) { 187 | yield data; 188 | if (condition(data)) { 189 | break; 190 | } 191 | } 192 | } 193 | 194 | const take = function (flow, number) { 195 | let count = 0; 196 | const _filter = function (data) { 197 | count ++ 198 | return count >= number; 199 | } 200 | return stop(flow, _filter); 201 | } 202 | ``` 203 | 204 | 对了还得加上链式调用才算是完成了。新建一个类,内部管理一个生成器就行了。每次调用返回 this。 205 | 206 | ```js 207 | class _Lazy{ 208 | constructor() { 209 | this.iterator = null; 210 | } 211 | 212 | range(...args) { 213 | this.iterator = range(...args); 214 | return this; 215 | } 216 | 217 | map(...args) { 218 | this.iterator = map(this.iterator, ...args); 219 | return this; 220 | } 221 | 222 | filter(...args) { 223 | this.iterator = filter(this.iterator, ...args); 224 | return this; 225 | } 226 | 227 | take(...args) { 228 | this.iterator = take(this.iterator, ...args); 229 | return this; 230 | } 231 | 232 | [Symbol.iterator]() { 233 | return this.iterator; 234 | } 235 | 236 | } 237 | 238 | function lazy () { 239 | return new _Lazy(); 240 | } 241 | ``` 242 | 243 | 最后再测试一下 244 | 245 | ```js 246 | const nums = lazy().range(0, 100).map(n => n * 10).filter(n => n % 3 === 0).take(2); 247 | 248 | for(let n of nums) { 249 | console.log('num:\t', n, '\n'); 250 | } 251 | /* 输出 252 | range 0 253 | map 0 254 | filter 0 255 | num: 0 256 | 257 | range 1 258 | map 1 259 | filter 10 260 | range 2 261 | map 2 262 | filter 20 263 | range 3 264 | map 3 265 | filter 30 266 | num: 30 267 | */ 268 | ``` 269 | 270 | 输出和预期的一样,只用了不到80行就实现了一个基本的最简单的数组惰性求值库。 271 | --------------------------------------------------------------------------------