一切为了抽象
一段代码越抽象它的复用价值就越高,举一个极端例子:
function emptyAction(x) { return x }
上面这个函数就像数学里面的 f(x) = x 一样,与山河同在,与日月同辉!
比如可以使用它进行数组的复制:
let a = [1, 2, 3]
let aDuplicate = a.map(emptyAction)
比如可以玩我返回我自己,虽然我也不知道这有什么用:
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
来源:oschina
链接:https://my.oschina.net/u/3320818/blog/4283846