js可迭代对象

181 阅读4分钟
  • 概念:js特殊对象类型,可被迭代(遍历)并访问其中的元素。是具有[Symbol.iterator]属性的对象,该属性是函数,返回一个迭代器对象。迭代器对象包含next()方法,用于逐个访问可迭代对象中的元素。
  • 任何对象都可以被定制为可在 for..of 循环中使用的对象。
  • 添加[Symbol.iterator]属性,这个属性是个方法,这个方法必须返回一个迭代器(iterator)-一个具有next方法的对象。
    • next()方法返回{done: Boolean, value: any},done=true时,循环结束,否则value是下一个值。
let range = {
    from: 1,
    to: 5,
  };

  range[Symbol.iterator] = function () {
    return {
      current: this.from,
      last: this.to,
      next() {
        if (this.current <= this.last) {
          return {
            done: false,
            value: this.current++,
          };
        } else {
          return { done: true };
        }
      },
    };
  };

  for (let num of range) {
    console.log(num);
  }
  • 关注点分离
    • range 自身没有 next() 方法。
    • 相反,是通过调用 rangeSymbol.iterator 创建了另一个对象,即所谓的“迭代器”对象,并且它的 next 会为迭代生成值。
    • 因此,迭代器对象和与其进行迭代的对象是分开的。
  • 可以合并,使用range自身作为迭代器
let 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 };
    }
  }
};

for (let num of range) {
  alert(num); // 1, 然后是 2, 3, 4, 5
}
    • 缺点是,现在不可能同时在对象上运行两个 for..of 循环了:它们将共享迭代状态,因为只有一个迭代器,即对象本身
    • 但是两个并行的 for..of 是很罕见的,即使在异步情况下
  • 字符串可以迭代(包括UTF-16 的扩展字符)
let str = '𝒳😂';
for (let char of str) {
    alert( char ); // 𝒳,然后是 😂
}
  • 显式调用迭代器
let str = "Hello";

// 和 for..of 做相同的事
// for (let char of str) alert(char);

let iterator = str[Symbol.iterator]();

while (true) {
  let result = iterator.next();
  if (result.done) break;
  alert(result.value); // 一个接一个地输出字符
}
    • 很少需要我们这样做,但是比 for..of 给了我们更多的控制权。例如,我们可以拆分迭代过程:迭代一部分,然后停止,做一些其他处理,然后再恢复迭代
  • 可迭代和类数组

这两个官方术语看起来差不多,但其实大不相同。请确保你能够充分理解它们的含义,以免造成混淆。

    • Iterable 如上所述,是实现了 Symbol.iterator 方法的对象。
    • Array-like 是有索引和 length 属性的对象,所以它们看起来很像数组。
    • 可迭代对象和类数组对象通常都 不是数组,它们没有 push 和 pop 等方法
  • Array.from
    • 可以接受一个可迭代或类数组的值,并从中获取一个“真正的”数组。然后我们就可以对其调用数组方法了。
    • Array.from 的完整语法允许我们提供一个可选的“映射(mapping)”函数,thisArg 允许我们为该函数设置 this;
      • Array.from(obj[, mapFn, thisArg])
      • let arr = Array.from(range,num=> num * num);
    • 拆分字符。与 str.split 方法不同,它依赖于字符串的可迭代特性,可以正确处理UTF-16 扩展字符
      • let str ='𝒳😂';
      • // 将 str 拆分为字符数组
      • let chars = Array.from(str);
      • alert(chars[0]);// 𝒳
      • alert(chars[1]);// 😂
    • 基于 Array.from 创建处理 UTF-16 扩展字符的 slice 方法
function slice(str, start, end) {
  return Array.from(str).slice(start, end).join('');
}

let str = '𝒳😂𩷶';

alert( slice(str, 1, 3) ); // 😂𩷶

// 原生方法不支持识别代理对(译注:UTF-16 扩展字符)
alert( str.slice(1, 3) ); // 乱码(两个不同 UTF-16 扩展字符碎片拼接的结果)
  • 总结:
    • 可以应用 for..of 的对象被称为 可迭代的
    • 技术上来说,可迭代对象必须实现 Symbol.iterator 方法。
      • objSymbol.iterator 的结果被称为 迭代器(iterator) 。由它处理进一步的迭代过程。
      • 一个迭代器必须有 next() 方法,它返回一个 {done: Boolean, value: any} 对象,这里 done:true 表明迭代结束,否则 value 就是下一个值。
    • Symbol.iterator 方法会被 for..of 自动调用,但我们也可以直接调用它。
    • 内建的可迭代对象例如字符串和数组,都实现了 Symbol.iterator。
    • 字符串迭代器能够识别代理对(surrogate pair)。(译注:代理对也就是 UTF-16 扩展字符。)

有索引属性和 length 属性的对象被称为 类数组对象。这种对象可能还具有其他属性和方法,但是没有数组的内建方法。

如果我们仔细研究一下规范 —— 就会发现大多数内建方法都假设它们需要处理的是可迭代对象或者类数组对象,而不是“真正的”数组,因为这样抽象度更高。

Array.from(obj[, mapFn, thisArg]) 将可迭代对象或类数组对象 obj 转化为真正的数组 Array,然后我们就可以对它应用数组的方法。可选参数 mapFn 和 thisArg 允许我们将函数应用到每个元素。