Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Iterable / Generator #22

Open
canvascat opened this issue Jul 13, 2021 · 0 comments
Open

Iterable / Generator #22

canvascat opened this issue Jul 13, 2021 · 0 comments

Comments

@canvascat
Copy link
Owner

Iterable / for...of

可迭代(Iterable) 对象是数组的泛化。这个概念是说任何对象都可以被定制为可在 for..of 循环中使用的对象。当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,下面将一个普通对象转化为可迭代对象:

// 比如一个 `range` 对象,它代表了一个数字区间:
const range = {
  from: 1,
  to: 5,
};

// 我们希望 for..of 这样运行:
// for(let num of range) ... num=1,2,3,4,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) {
  console.log(num); // 依次输出 1, 2, 3, 4, 5
}

为了让 range 对象可迭代(也就让 for..of 可以运行)我们需要为对象添加一个名为 Symbol.iterator 的方法(一个专门用于使对象可迭代的内置 symbol)。

  1. for..of 循环启动时,它会调用这个方法(如果没找到,就会报错)。这个方法必须返回一个 迭代器(iterator) —— 一个有 next 方法的对象。
  2. 从此开始,for..of 仅适用于这个被返回的对象
  3. for..of 循环希望取得下一个数值,它就调用这个对象的 next() 方法。
  4. next() 方法返回的结果的格式必须是 {done: Boolean, value: any},当 done=true 时,表示迭代结束,否则 value 是下一个值。

请注意可迭代对象的核心功能:关注点分离。

  • range 自身没有 next() 方法。
  • 相反,是通过调用 range[Symbol.iterator]() 创建了另一个对象,即所谓的“迭代器”对象,并且它的 next 会为迭代生成值。

因此,迭代器对象和与其进行迭代的对象是分开的。

从技术上说,我们可以将它们合并,并使用 range 自身作为迭代器来简化代码:

const range = {
  from: 1,
  to: 5,

  [Symbol.iterator]() {
    this.current = this.from;
    return this;
  },

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

现在 range[Symbol.iterator]() 返回的是 range 对象自身:它包括了必需的 next() 方法,并通过 this.current 记忆了当前的迭代进程。这样更短,对吗?是的。有时这样也可以。

但缺点是,现在不可能同时在对象上运行两个 for..of 循环了:它们将共享迭代状态,因为只有一个迭代器,即对象本身。但是两个并行的 for..of 是很罕见的,即使在异步情况下。

原生具备 Iterator 接口的数据结构如下:

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

对于一个字符串,for..of 遍历它的每个字符:

for (let char of 'test') {
  // 触发 4 次,每个字符一次
  console.log(char); // t, then e, then s, then t
}
// 对于Unicode字符
for (let char of '𝒳😂') {
  console.log(char); // 𝒳,然后是 😂
}

显式调用迭代器

const str = 'Hello';
// 和 for..of 做相同的事
// for (let char of str) console.log(char);

const iterator = str[Symbol.iterator]();
while (true) {
  const result = iterator.next();
  if (result.done) break;
  console.log(result.value); // 一个接一个地输出字符
}

显式调用迭代器比使用 for..of 更能精细控制迭代过程。例如,我们可以拆分迭代过程:迭代一部分,然后停止,做一些其他处理,然后再恢复迭代。

可迭代(iterable)和类数组(array-like)

有两个看起来很相似,但又有很大不同的正式术语。请你确保正确地掌握它们,以免造成混淆。

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

实际任务中我们可能会遇到可迭代对象或类数组对象,或两者兼有。例如,字符串即是可迭代的(for..of 对它们有效),又是类数组的(它们有数值索引和 length 属性)。但是一个可迭代对象也许不是类数组对象。反之亦然,类数组对象可能不可迭代。

可迭代对象和类数组对象通常都 不是数组,它们没有 pushpop 等方法。如果我们有一个这样的对象,并想像数组那样操作它,就可以通过一些其他方法将其转化为数组。

Array.from

const arr = Array.from({
  0: 'Hello',
  1: 'World',
  length: 2,
});
console.log(arr.pop()); // World(pop 方法有效)
// range 来自上文的例子中
console.log(Array.from(range).toString()); // 1,2,3,4,5

另外用Array.from处理带 Unicode 的字符是非常方便的,与 str.split 方法不同,它依赖于字符串的可迭代特性。

可以基于 Array.from 创建 UTF-16 扩展字符的slice 方法:

const slice(str, start, end) => Array.from(str).slice(start, end).join('');

const str = '𝒳😂𩷶';
console.log(slice(str, 1, 3)); // 😂𩷶
// 原生方法不支持识别UTF-16 扩展字符
console.log( str.slice(1, 3) ); // "\udcb3\ud83d"(两个不同 UTF-16 扩展字符碎片拼接的结果)

另外解构也是类似原理:

console.log([...'𝒳😂𩷶']); // => ["𝒳", "😂", "𩷶"]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant