一、Symbol.iterator
可迭代(Iterable) 对象是数组的泛化。这个概念是说任何对象都可以被定制为可在 for..of 循环中使用的对象。
let range = {
from: 1,
to: 5
};
// 1. for..of 调用首先会调用这个:
range[Symbol.iterator] = function() {
// ……它返回迭代器对象(iterator object):
// 2. 接下来,for..of 仅与下面的迭代器对象一起工作,要求它提供下一个值
return {
current: this.from,
last: this.to,
// 3. next() 在 for..of 的每一轮循环迭代中被调用
next() {
// 4. 它将会返回 {done:.., value :...} 格式的对象
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};
// 现在它可以运行了!
for (let num of range) {
alert(num); // 1, 然后是 2, 3, 4, 5
}
二、字符串是可迭代的
数组和字符串是使用最广泛的内建可迭代对象。
let str = '𝒳😂';
for (let char of str) {
alert( char ); // 𝒳,然后是 😂
}
三、可迭代和类数组
- Iterable 如上所述,是实现了 Symbol.iterator 方法的对象。
- Array-like 是有索引和 length 属性的对象,所以它们看起来很像数组。
两者没有必然关联,字符串即是可迭代的(for..of 对它们有效),又是类数组的(有数值索引和 length 属性)
四、Array.from
可迭代对象和类数组对象通常都不是数组,它们没有 push 和 pop 等方法。但有一个全局方法 Array.from 可以接受一个可迭代或类数组的值,并从中获取一个真正的数组。
Array.from 的完整语法允许我们提供一个可选的“映射(mapping)”函数:
Array.from(obj[, mapFn, thisArg])
可选的第二个参数 mapFn 可以是一个函数,该函数会在对象中的元素被添加到数组前,被应用于每个元素,此外 thisArg 允许我们为该函数设置 this。 ```typescript // 求每个数的平方 let arr = Array.from(“12345”, num => num * num);
alert(arr); // 1,4,9,16,25
注意:string.split('') 与 Array.from(string) 的区别:
- Array.from 能正常处理 UTF-16 的扩展字符,即代理对(surrogate pair)
```typescript
"👩2X".split('') // ['\uD83D', '\uDC69', '2', 'X']
Array.from("👩2X") // ['👩', '2', 'X']
- Array.from 可创建代理感知(surrogate-aware)的slice 方法(即能处理 UTF-16 扩展字符的 slice 方法) ```typescript function slice(str, start, end) { return Array.from(str).slice(start, end).join(‘’); }
let str = ‘𝒳😂𩷶’;
alert( slice(str, 1, 3) ); // 😂𩷶 alert( str.slice(1,3) ); // ‘\uDCB3\uD83D’
<a name="SF34T"></a>
## 五、Generator
迭代器 Generator 是通过迭代器函数 function* f(…) {…} 创建的。
<a name="JpzcH"></a>
### 1. Generator 函数
Generator 函数与常规函数的行为不同。在此类函数被调用时,它不会运行其代码。而是返回一个被称为 “generator object” 的特殊对象,来管理执行流程。
```typescript
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
// "generator function" 创建了一个 "generator object"
let generator = generateSequence();
alert(generator); // [object Generator]
let one = generator.next();
alert(JSON.stringify(one)); // {value: 1, done: false}
let two = generator.next();
alert(JSON.stringify(two)); // {value: 2, done: false}
let three = generator.next();
alert(JSON.stringify(three)); // {value: 3, done: true}
一个 generator 的主要方法就是 next()。当被调用时,它会执行直到最近的 yield
- next() 的结果始终是一个具有两个属性的对象:
- value: 产出的(yielded)的值。
- done: 如果 generator 函数已执行完成则为 true,否则为 false。
- generator 执行完成后再对 generator.next() 进行新的调用不再有任何意义。它将返回相同的对象:{done: true}
2. Generator 是可迭代的
- 可以使用 for..of 循环遍历它所有的值。但当 done: true 时,for..of 循环会忽略最后一个 value。因此,如果我们想要通过 for..of 循环显示所有的结果,我们必须使用 yield 返回它们
- 可以使用 iterator 的所有相关功能,例如:spread 语法 … ```typescript function* generateSequence() { yield 1; yield 2; yield 3; }
let sequence = [0, …generateSequence()];
alert(sequence); // 0, 1, 2, 3
<a name="cSNNs"></a>
### 3. 使用 generator 进行迭代
通过提供一个 generator 函数作为 Symbol.iterator,来使用 generator 进行迭代:
```javascript
let range = {
from: 1,
to: 5,
*[Symbol.iterator]() {
for(let value = this.from; value <= this.to; value++) {
yield value;
}
}
};
[...range] // [1,2,3,4,5]
六、惰性求值
惰性求值(英语:Lazy Evaluation),又译为惰性计算、懒惰求值,也称为传需求调用(call-by-need),是一个计算机编程中的一个概念,它的目的是要最小化计算机要做的工作。它有两个相关而又有区别的含意,可以表示为“延迟求值”和“最小化求值”,除可以得到性能的提升外,惰性计算的最重要的好处是它可以构造一个无限的数据类型。
实现方式
惰性求值每次求值返回一个包含计算参数的求值函数,要使用值得时候才进行计算。当有多个惰性操作的时候,构成一个求值函数链,每个求值函数都向上一个求值函数求值返回。最后计算函数终止时返回一个终止值。
迭代器实现
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;
}
}
}
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;
}
[Symbol.iterator]() {
return this.iterator;
}
}
function lazy () {
return new _Lazy();
}
const nums = lazy().range(0, 10).map(n => n * 10).filter(n => n % 3 === 0)
console.log([...nums]) //[0, 30, 60, 90]
参考资料
lodash的lazyValue(惰性求值)
你觉得“惰性求值”在 JS 中会怎么实现?
Javascript中的求值策略
如何用 JavaScript 实现一个数组惰性求值库