一切为了抽象
一段代码越抽象它的复用价值就越高,举一个极端例子: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