目录

🔀 JavaScript 可迭代对象

可迭代对象 是数组的泛化,任何对象都可以被定制为可在 for..of 循环中使用的对象。许多内建对象都可迭代,除了数组,还有字符串。

# Symbol.iterator

🌰 例子:使自定义的对象可迭代:

let range = {
  from: 1,
  to: 5
}
// 希望得到 for(let num of range) ... num=1,2,3,4,5
1
2
3
4
5

为了让 range 可迭代, 需要为对象添加 Symbol.iterator (使对象可迭代的 内建 symbol

  • for..of 循环启动时,它会调用这个方法(如果没找到,就会报错)。这个方法必须返回一个 迭代器(iterator) ( 一个有 next 方法的对象)。
  • 从此开始, for..of 仅适用于这个被返回的对象
  • for..of 循环希望取得下一个数值,它就调用这个对象的 next() 方法。
  • next() 方法返回的结果的格式必须是 {done: Boolean, value: any} ,当 done=true 时,表示循环结束,否则 value 是下一个值。
// 1.为 range 添加 Symbol.iterator
range[Symbol.iterator] = function() {
  // 返回一个迭代器对象
  // 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...of循环
for(let num of range) {
  console.log(num)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

迭代器的核心功能:关注点分离

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

进一步简化,将 Symbol.iterator 合并进对象,使用 range 自身作为迭代器。

let range = {
  from: 1,
  to: 5,
  
  [System.iterator]() {
    this.current = this.from,
    return this
  },
  
  next() {
    if(this.current <= this.to) {
      return {done: false, value: this.current++}
    } else {
      return {done: true}
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

现在 range[Symbol.iterator]() 返回的是 range 对象自身:它包括了必需的 next() 方法,并通过 this.current 记忆了当前的迭代进程。

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

无穷的迭代器:将 range 设置为 range.to = Infinity ,这时 range 则成为了无穷迭代器。或者可以创建一个可迭代对象,它生成一个无穷伪随机数序列。

next 没有什么限制,它可以返回越来越多的值,这是正常的。

迭代这种对象的 for..of 循环将不会停止。但是可以通过使用 break 来停止它。

# 可迭代字符串

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

🌰 例子:

for (let char of "test") {
  console.log( char );
}
1
2
3

🌰 例子 / for…of 可处理代理对:

let smileString = '😀😃😄'

for (let smile of smileString) {
	console.log(smile) // "😀" "😃" "😄"
}
1
2
3
4
5

# 显式调用迭代器

使用 与 for..of 完全相同 的方式遍历字符串,但是使用的是 直接调用。这段代码创建了一个字符串迭代器,并「手动」从中获取值。

let str = 'Hello'

let iterator = str[Symbol.iterator]

while(true) {
  let result = iterator.next()
  if (result.done) break;
  console.log(result,value)
}
1
2
3
4
5
6
7
8
9

此过程,比 for..of 给了更多的控制权。例如,可以拆分迭代过程:迭代一部分,然后停止,做一些其他处理,然后再恢复迭代。

# 可迭代与类数组

  • 可迭代 Interable: 是实现了 Symbol.iterator 方法的对象。
  • 类数组 Array-like:是有索引和 length 属性的对象,所以它们看起来很像数组。

当将 JavaScript 用于编写 在浏览器或任何其他环境中 的实际任务时,可能会遇到 可迭代对象或类数组对象,或两者兼有。例如, 字符串 既是可迭代的,又是类数组的。

** 但是,一个可迭代对象也许不是类数组对象。反之亦然,类数组对象可能不可迭代。** 例如,第一个例子 range 对象,迭代的,但并非类数组对象,因为它没有索引属性,也没有 length 属性。

🌰 例子 / 类数组但不可迭代:

let arrayLike = {
  0: 'hello',
  1: 'world',
  length: 2
}

// for (let item of arraylike) {}
1
2
3
4
5
6
7

# Array.from

对于可迭代对象和类数组对象通常都 不是数组,它们没有 pushpop 等方法。如果想引入数组的方法操作 range 对象:

全局方法 Array.from (opens new window) 可以 接受一个可迭代或类数组的值,并从中获取一个「真正的」数组,然后就可以对其调用数组方法了。

🌰 例子:

let arrayLike = {
  0: 'hello',
  1: 'world',
  length: 2
}

let arr = Array.from(arrayLike) 
console.log(arr.pop()) // world 数组方法有效
1
2
3
4
5
6
7
8

Array.from 方法接受对象,检查它是一个可迭代对象或类数组对象,然后创建一个新数组,并将该对象的所有元素 复制 到这个新数组。

🌰 例子 / 处理一个数组转换为单个字符的数组:

let smileString = '😀😃😄'
let arraySmile = Array.from(smileString)
console.log(arrayString) // ["😀", "😃", "😄"]
1
2
3

str.split() 方法不同,它依赖于字符串的可迭代特性,因此,就像 for..of 一样,可以正确地处理代理对(surrogate pair)。

let splitSmile = smile.split('')
console.log(splitSmile) // ["�", "�", "�", "�", "�", "�"]
1
2

split 无法处理代理对(surrogate pair)。

实际上,可以看作 for…of 的工作:

let arraySmile = []
let (smile of smileString) {
  arraySmile.push(smile)
}
1
2
3
4

🌰 例子 / 基于 Array.from 创建代理感知(surrogate-aware)的 slice 方法,解决原生 slice 不支持识别代理对:

function slice(str, start, end) {
    return Array.from(str).slice(start, end).join('');
}

let str = '𝒳😂𩷶'
console.log(slice(str, 1, 3)) // "😂𩷶"
1
2
3
4
5
6

🌰 例子 / 使用展开运算符也可转换为真正的数组:

let str = "𝒳😂";
let chars = [...str];

console.log(chars[0]); // 𝒳
console.log(chars[1]); // 😂
console.log(chars.length); // 2
1
2
3
4
5
6

# 可迭代对象总结

  • 🍎 可以应用 for..of 的对象被称为 可迭代的

    • 可迭代对象必须实现 Symbol.iterator 方法。
    • obj[Symbol.iterator]() 的结果被称为 迭代器(iterator)。由它处理进一步的迭代过程。
    • 一个迭代器必须有 next() 方法,它返回一个 {done: Boolean, value: any} 对象,这里 done:true 表明迭代结束,否则 value 就是下一个值。
  • Symbol.iterator 方法会被 for..of 自动调用或者可以直接调用。

  • 内建的可迭代对象例如字符串和数组,都实现了 Symbol.iterator

  • 字符串迭代器能够 识别代理对(surrogate pair)。

  • 类数组对象:有索引属性和 length 属性的对象,但是 没有数组的内建方法

    大多数内建方法都假设它们需要处理的是可迭代对象或者类数组对象,而不是「真正的」数组,因为这样 抽象度更高

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

📢 上次更新: 2022/09/02, 10:18:16