JavaScript 中的可迭代对象与迭代器

更新日期: 2021-08-09 阅读: 2k 标签: 对象

Lazy evaluation

Lazy evaluation常被译为“延迟计算”或“惰性计算”,指的是仅仅在真正需要执行的时候才计算表达式的值。与惰性求值相反的是及早求值(eager evaluation)及早求值,也被称为贪婪求值(greedy evaluation)或严格求值,是多数传统编程语言的求值策略。

充分利用惰性求值的特性带来的好处主要体现在以下两个方面:

  • 避免不必要的计算,带来性能上的提升。
  • 节省空间,使得无限循环的数据结构成为可能。

迭代器

ES6 中的迭代器使惰性求值和创建用户定义的数据序列成为可能。迭代是一种遍历数据的机制。 迭代器是用于遍历数据结构元素(称为Iterable)的指针,用于产生值序列的指针。

迭代器是一个可以被迭代的对象。它抽象了数据容器,使其行为类似于可迭代对象。

迭代器在实例化时不计算每个项目的值,仅在请求时才生成下一个值。 这非常有用,特别是对于大型数据集或无限个元素的序列。

可迭代对象

可迭代对象是希望其元素可被公众访问的数据结构。JS 中的很多对象都是可迭代的,它们可能不是很好的察觉,但是如果仔细检查,就会发现迭代的特征:

  • new Map([iterable])
  • new WeakMap([iterable])
  • new Set([iterable])
  • new WeakSet([iterable])
  • Promise.all([iterable])
  • Promise.race([iterable])
  • Array.from([iterable])

还有需要一个可迭代的对象,否则,它将抛出一个类型错误,例如:

  • for ... of
  • ... (展开操作符)
    const [a, b, ..] = iterable (解构赋值)
  • yield* (生成器)

JavaScript中已有许多内置的可迭代项:

String,Array,TypedArray,Map,Set。

迭代协议

迭代器和可迭对象遵循迭代协议。

协议是一组接口,并规定了如何使用它们。

迭代器遵循迭代器协议,可迭代遵循可迭代协议。

可迭代的协议

要使对象变得可迭代,它必须实现一个通过Symbol.iterator的迭代器方法,这个方法是迭代器的工厂。

使用 TypeScript,可迭代协议如下所示:

interface Iterable {
  [Symbol.iterator]() : Iterator;
}

Symbol.iterator]()是无参数函数。 在可迭代对象上调用它,这意味着我们可以通过this来访问可迭代对象,它可以是常规函数或生成器函数。

迭代器协议

迭代器协议定义了产生值序列的标准方法。

为了使对象成为迭代器,它必须实现next()方法。 迭代器可以实现return()方法,我们将在本文后面讨论这个问题。

使用 TypeScript,迭代器协议如下所示:

interface Iterator {
    next() : IteratorResult;
    return?(value?: any): IteratorResult;
}

IteratorResult 的定义如下:

interface IteratorResult {
    value?: any;
    done: boolean;
}
  • done通知消费者迭代器是否已经被使用,false表示仍有值需要生成,true表示迭代器已经结束。
  • value 可以是任何 JS 值,它是向消费者展示的值。

当done为true时,可以省略value。

组合

迭代器和可以可迭代对象可以用下面这张图来表示:


事例

基础知识介绍完了,接着,我们来配合一些事例来加深我们的映像。

范围迭代器

我们先从一个非常基本的迭代器开始,createRangeIterator迭代器。

我们手动调用it.next()以获得下一个IteratorResult。 最后一次调用返回{done:true},这意味着迭代器现在已被使用,不再产生任何值。

function createRangeIterator(from, to) {
  let i = from;

  return {
    next() {
      if (i <= to) {
        return { value: i++, done: false };
      } else {
        return { done: true };
      }
    }
  }
}

const it = createRangeIterator(1, 3);

console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());


可迭代范围迭代器

在本文的前面,我已经提到 JS 中的某些语句需要一个可迭代的对象。 因此,我们前面的示例在与for ... of循环一起使用时将不起作用。

但是创建符合迭代器和可迭代协议的对象非常容易。


function createRangeIterator (from, to) {
  let i = from

  return {
    [Symbol.iterator] () {
      return this
    },
    next() {
      if (i <= to) {
        return { value: i++, done: false }
      } else {
        return { done: true }
      }
    }
  }
}

const it = createRangeIterator(1, 3)

for (const i of it) {
  console.log(i)
}

无限序列迭代器

迭代器可以表示无限制大小的序列,因为它们仅在需要时才计算值。

注意不要在无限迭代器上使用扩展运算符(...),JS 将尝试消费迭代器,由于迭代器是无限的,因此它将永远不会结束。 所以你的应用程序将崩溃,因为内存已被耗尽

同样,for ... of 循环也是一样的情况,所以要确保能退出循环:

function createEvenNumbersIterator () {
  let value = 0

  return {
    [Symbol.iterator] () {
      return this
    },
    next () {
      value += 2
      return { value, done: false}
    }
  }
}

const it = createEvenNumbersIterator()

const [a, b, c] = it
console.log({a, b, c})

const [x, y, z] = it
console.log({ x, y, z })

for (const even of it) {
  console.log(even)
  if (even > 20) {
    break
  }
}


关闭迭代器

前面我们提到过,迭代器可以有选择地使用return()方法。 当迭代器直到最后都没有迭代时使用此方法,并让迭代器进行清理。

for ... of循环可以通过以下方式更早地终止迭代:

  • break
  • continue
  • throw
  • return
function createCloseableIterator () {
  let idx = 0
  const data = ['a', 'b', 'c', 'd', 'e']

  function cleanup() {
    console.log('Performing cleanup')
  }
  return {
    [Symbol.iterator]() { return this },
    next () {
      if (idx <= data.length - 1) {
        return { value: data[idx++], done: false }
      } else {
        cleanup()
        return { done: true }
      }
    },
    return () {
      cleanup()
      return { done: true }
    }
  }
}

const it = createCloseableIterator()

for (const value of it) {
  console.log(value)
  if (value === 'c') {
    break
  }
}

console.log('\n----------\n')

const _it = createCloseableIterator();
for (const value of _it) {
  console.log(value);
}


  • 如果知道迭代器已经结束,则手动调用cleanup()函数。
  • 如果突然完成,则return()起作用并为我们进行清理。

额外的内容

如果你已经做到了这一点,我们来看看一些额外的内容。

组合器

组合器是将现有可迭代对象组合在一起以创建新可迭代对象的函数。

因此,我们能够创建许多实用函数。那map或者filter呢?看看下面的代码,花一分钟时间来理解它。

function createEvenNumbersIterator() {
  let value = 0;

  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      value += 2;
      return { value, done: false };
    }
  }
}

function map(fn, iterable) {
  const iter = iterable[Symbol.iterator]();

  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      const n = iter.next();
      if (!n.done) {
        return { value: fn(n.value), done: false };
      } else {
        return { done: true };
      }
    }
  }
}

function filter(fn, iterable) {
  const iter = iterable[Symbol.iterator]();

  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      const n = iter.next();
      if (!n.done) {
        if (fn(n.value)) {
          return { value: n.value, done: false };
        } else {
          return this.next();
        }
      } else {
        return { done: true };
      }
    }
  }
}

function take(n, iterable) {
  const iter = iterable[Symbol.iterator]();

  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      if (n > 0) {
        n--;
        return iter.next();
      } else {
        return { done: true };
      }
    }
  }
}

function cycle(iterable) {
  const iter = iterable[Symbol.iterator]();
  const saved = [];
  let idx = 0;
  
  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      const n = iter.next();
      if (!n.done) {
        saved[idx++] = n.value;
        return { value: n.value, done: false };
      } else {
        return { value: saved[idx++ % saved.length], done: false };
      }
    }
  }
}

function collect(iterable) {
  // consumes the iterator
  return Array.from(iterable);
}

const evenNumbersIterator = createEvenNumbersIterator();
const result = collect(                 // 7. and collect the result
  filter(                               // 6. keep only values higher than 1
    val => val > 1, map(                // 5. divide obtained values by 2
      val => val / 2, take(             // 4. take only six of them
        6, cycle(                       // 3. make an infinite cycling sequence of them
          take(                         // 2. take just three of them
            3, evenNumbersIterator      // 1. infinite sequence of even numbers
          )
        )
      )
    )
  )
);

console.log(result);

这是一大堆代码,很快我们将看到如何使用生成器和函数式编程概念来重构所有这些内容。保持关注,并注意我的后续文章,我们仍然有很多内容要讲。

原文:https://medium.com/@MelrNemesis/javascript-lazy-evaluation-iterables-iterators-e0770a5de96f


本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/10613

相关推荐

js获取object对象的长度

我们都知道必须是具体数据类型才有长度,所以size和length都无法测量object对象的长度,那么如何计算对象的长度,即获取对象属性的个数呢?

JavaScript数组、对象合并的多种方法实现

这篇文章讲解Js数组和对象的一些使用技巧,如何将不同的数组,对象合并/结合为1个的方法

js中关于for...in遍历对象属性的顺序问题

对象使用obj.length时,它得到的值是undefined的,所以只能通过for...in循环获取对象的属性,我们发现并没有按属性的顺序显示,而且顺序在各个浏览器下显示也不同。 这是为什么呢?

JavaScript 判断对象中是否有某属性

判断对象中是否有某属性的常见方式总结,不同的场景要使用不同的方式。一点( . )或者方括号( [ ] )、二in 运算符、三hasOwnProperty()。三种方式各有优缺点,不同的场景使用不同的方式,有时还需要结合使用

JS 的 Document对象

Document 对象是是window对象的一个属性,因此可以将document对象作为一个全局对象来访问。当浏览器载入 HTML 文档, 它就会成为 Document 对象。Document对象的 属性和方法

Js通过.或者[]访问对象属性的语法、性能等区别

在JavaScript中可以使用 . 或者 [ ] 来访问对象的属性,但是对象中方法只能通过 . 来获取;使用.运算符来存取对象的属性的值。或者使用[]作为一个关联数组来存取对象的属性。但是这两种方式有什么区别了?

history对象详解及单页面路由实现

history对象保存着用户的上网记录,从浏览器窗口打开的那一刻算起。出于安全的考虑,开发人员无法得知用户浏览过的URL。不过,借由用户访问过的页面列表,同样可以在不知道实际URL的情况下实现后退与前进

js对象 对属性调用.和[] 两种方式的区别

在 JS 对象中,调用属性一般有两种方法——点和中括号的方法。 标准格式是对象.属性(不带双引号),注意一点的是:js对象的属性,key标准是不用加引号的,加也可以,特别的情况必须加,如果key数字啊,表达式啊等等

javascript中document是什么?

javascript中document是window对象的属性,表示对Document对象的只读引用。Document对象是Window对象的一部分,可通过window.document属性对其进行访问。

Js中Blob是什么?

MDN给出的解释:Blob 对象表示一个不可变、原始数据的类文件对象;创建一个blob只有两种方式1、通过new Blob();使用blob.slice切割,创建一个新的blob对象;读取blob唯一方式,使用fileReader

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!