一、Symbol.iterator

可迭代(Iterable) 对象是数组的泛化。这个概念是说任何对象都可以被定制为可在 for..of 循环中使用的对象。

  1. let range = {
  2. from: 1,
  3. to: 5
  4. };
  5. // 1. for..of 调用首先会调用这个:
  6. range[Symbol.iterator] = function() {
  7. // ……它返回迭代器对象(iterator object):
  8. // 2. 接下来,for..of 仅与下面的迭代器对象一起工作,要求它提供下一个值
  9. return {
  10. current: this.from,
  11. last: this.to,
  12. // 3. next() 在 for..of 的每一轮循环迭代中被调用
  13. next() {
  14. // 4. 它将会返回 {done:.., value :...} 格式的对象
  15. if (this.current <= this.last) {
  16. return { done: false, value: this.current++ };
  17. } else {
  18. return { done: true };
  19. }
  20. }
  21. };
  22. };
  23. // 现在它可以运行了!
  24. for (let num of range) {
  25. alert(num); // 1, 然后是 2, 3, 4, 5
  26. }

二、字符串是可迭代的

数组和字符串是使用最广泛的内建可迭代对象。

  1. let str = '𝒳😂';
  2. for (let char of str) {
  3. alert( char ); // 𝒳,然后是 😂
  4. }

三、可迭代和类数组

  • Iterable 如上所述,是实现了 Symbol.iterator 方法的对象。
  • Array-like 是有索引和 length 属性的对象,所以它们看起来很像数组。

两者没有必然关联,字符串即是可迭代的(for..of 对它们有效),又是类数组的(有数值索引和 length 属性)

四、Array.from

可迭代对象和类数组对象通常都不是数组,它们没有 push 和 pop 等方法。但有一个全局方法 Array.from 可以接受一个可迭代或类数组的值,并从中获取一个真正的数组。

  • Array.from 的完整语法允许我们提供一个可选的“映射(mapping)”函数:

    1. Array.from(obj[, mapFn, thisArg])
  • 可选的第二个参数 mapFn 可以是一个函数,该函数会在对象中的元素被添加到数组前,被应用于每个元素,此外 thisArg 允许我们为该函数设置 this。 ```typescript // 求每个数的平方 let arr = Array.from(“12345”, num => num * num);

alert(arr); // 1,4,9,16,25

  1. 注意:string.split('') Array.from(string) 的区别:
  2. - Array.from 能正常处理 UTF-16 的扩展字符,即代理对(surrogate pair)
  3. ```typescript
  4. "👩2X".split('') // ['\uD83D', '\uDC69', '2', 'X']
  5. 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’

  1. <a name="SF34T"></a>
  2. ## 五、Generator
  3. 迭代器 Generator 是通过迭代器函数 function* f(…) {…} 创建的。
  4. <a name="JpzcH"></a>
  5. ### 1. Generator 函数
  6. Generator 函数与常规函数的行为不同。在此类函数被调用时,它不会运行其代码。而是返回一个被称为 “generator object” 的特殊对象,来管理执行流程。
  7. ```typescript
  8. function* generateSequence() {
  9. yield 1;
  10. yield 2;
  11. return 3;
  12. }
  13. // "generator function" 创建了一个 "generator object"
  14. let generator = generateSequence();
  15. alert(generator); // [object Generator]
  16. let one = generator.next();
  17. alert(JSON.stringify(one)); // {value: 1, done: false}
  18. let two = generator.next();
  19. alert(JSON.stringify(two)); // {value: 2, done: false}
  20. let three = generator.next();
  21. alert(JSON.stringify(three)); // {value: 3, done: true}

一个 generator 的主要方法就是 next()。当被调用时,它会执行直到最近的 yield 语句(value 可以被省略,默认为 undefined)。然后函数执行暂停,并将产出的(yielded)值返回到外部代码。

  • 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

  1. <a name="cSNNs"></a>
  2. ### 3. 使用 generator 进行迭代
  3. 通过提供一个 generator 函数作为 Symbol.iterator,来使用 generator 进行迭代:
  4. ```javascript
  5. let range = {
  6. from: 1,
  7. to: 5,
  8. *[Symbol.iterator]() {
  9. for(let value = this.from; value <= this.to; value++) {
  10. yield value;
  11. }
  12. }
  13. };
  14. [...range] // [1,2,3,4,5]

六、惰性求值

惰性求值(英语:Lazy Evaluation),又译为惰性计算、懒惰求值,也称为传需求调用(call-by-need),是一个计算机编程中的一个概念,它的目的是要最小化计算机要做的工作。它有两个相关而又有区别的含意,可以表示为“延迟求值”和“最小化求值”,除可以得到性能的提升外,惰性计算的最重要的好处是它可以构造一个无限的数据类型。

实现方式

惰性求值每次求值返回一个包含计算参数的求值函数,要使用值得时候才进行计算。当有多个惰性操作的时候,构成一个求值函数链,每个求值函数都向上一个求值函数求值返回。最后计算函数终止时返回一个终止值。

迭代器实现

  1. const range = function* (from, to) {
  2. for(let i = from; i < to; i++) {
  3. console.log('range\t', i);
  4. yield i;
  5. }
  6. }
  7. const map = function* (flow, transform) {
  8. for(const data of flow) {
  9. console.log('map\t', data);
  10. yield(transform(data));
  11. }
  12. }
  13. const filter = function* (flow, condition) {
  14. for(const data of flow) {
  15. console.log('filter\t', data);
  16. if (condition(data)) {
  17. yield data;
  18. }
  19. }
  20. }
  21. class _Lazy{
  22. constructor() {
  23. this.iterator = null;
  24. }
  25. range(...args) {
  26. this.iterator = range(...args);
  27. return this;
  28. }
  29. map(...args) {
  30. this.iterator = map(this.iterator, ...args);
  31. return this;
  32. }
  33. filter(...args) {
  34. this.iterator = filter(this.iterator, ...args);
  35. return this;
  36. }
  37. [Symbol.iterator]() {
  38. return this.iterator;
  39. }
  40. }
  41. function lazy () {
  42. return new _Lazy();
  43. }
  44. const nums = lazy().range(0, 10).map(n => n * 10).filter(n => n % 3 === 0)
  45. console.log([...nums]) //[0, 30, 60, 90]

参考资料

lodash的lazyValue(惰性求值)
你觉得“惰性求值”在 JS 中会怎么实现?
Javascript中的求值策略
如何用 JavaScript 实现一个数组惰性求值库