习惯了python、R等编程语言自带序列的写法,突然想把这样的行为移植到JS中,在NodeJS中,如果要快速生成一个[0-9]的数组,那么最快捷的写法是:
// 生成[0,1,2,3,4,5,6,7,8,9]
[...Array[10].keys()]
对于上面的写法,理解分为[]步:
1. Array[10]返回一个10个undefined元素的数组;
2. keys()返回数组元素的迭代器,用于对键名的遍历,对数组而言,也就是索引;
3. …代表扩展运算符,用于将数组转换为参数序列(请理解为apply函数),即0,1,2,3,4,5,6,7,8,9;
4. []代表调用Array()方法,最后当然返回一个10个元素的数组;
所以,上述的写法可改写为:
let array = Array(10),
indexs = array.keys(),
result = Array(...indexs)
// 输出结果
console.log(result)
那么我们要取10到20的序列,那么就可以采用类似的方法,如下:
[...Array(21).keys()].slice(11)
显然这样的写法实在太低效了,也太浪费存储空间了,并且无法定制步进,从而灵活性也不强,既然这样,那我们能不能利用类似扩展运算符的语法,用下面的写法定义自己的序列呢?
// 2代表起始数字,20代表结束数字,3代码步进
[...range(2, 20, 3)]
这里我们可以充分使用ES 6的iterator特性,定义如下的Range构造函数:
class Range {
constructor(start, end, step) {
this.start = start
this.end = end
this.step = step
}
/**
* 定义迭代器函数,是实现迭代器效果的关键
*/
[Symbol.iterator] () {
let curr = this.start,
_this = this
return {
next () {
let result = curr < _this.end ? {
value : curr,
done : false
} : {
value : undefined,
done : true
}
curr = curr + _this.step
return result
}
}
}
}
Symbol.iterator是扩展对象支持迭代模式枚举常量,必须以上述的方式进行引用,以保持对Symbol类型的唯一性。
上面的写法虽然实现了功能,但稍嫌啰嗦,我们可以用yield对其进行改进,如下:
[Symbol.iterator] () {
let curr = this.start,
_this = this
// 必须要添加*号
return function* () {
// 必须要使用循环
while(curr < _this.end) {
yield curr
curr = curr + _this.step
}
}() // 必须要对此函数进行执行
}
最后,我们只需要对其进行简单的包装即可完成所有的任务:
function range(start, end, step) {
return new Range(start, end, step)
}
现在我们就可以利用自定义序列了,如下:
// 输出[ 5, 10, 15, 20 ]
[...range(5, 25, 5)]
// 输出[ 100, 110, 120, 130, 140, 150, 160, 170, 180, 190 ]
[...range(100, 200, 10)]
结论
利用内置的Symbol.iterator与yield,我们可以轻易构造出自定义的序列数组。