skip to content
月与羽

类数组对象和可迭代对象

/ 11 min read

类数组对象(Array-Like)

类数组对象是具有 length 属性,并且属性名是数字(或可以转换为数字的字符串)的对象。它不是数组,但“看起来像”数组。

const arrayLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
console.log(arrayLike); // { '0': 'a', '1': 'b', '2': 'c', length: 3 }

区别对比

特性数组(Array)类数组对象(Array-Like)
构造函数Array()通常为 Object() 或自定义对象
原型链继承自 Array.prototype继承自 Object.prototype
常用方法push, pop, forEach, map没有数组方法,除非手动绑定或转换
length 属性动态变化不会自动随元素变化,需手动维护
typeof 判断'object''object'
Array.isArray()truefalse
遍历方式for...of, forEach 等可用只能用 for 循环或转换后遍历

如何将类数组对象转换为数组

类数组对象不能直接调用数组方法,但可以通过以下方式转换为真正的数组:

1. Array.from()

const arrayLike = { 0: 'a', 1: 'b', length: 2 };
const arr = Array.from(arrayLike);
console.log(arr); // ['a', 'b']

2. Array.prototype.slice.call()

const arrayLike = { 0: 'a', 1: 'b', length: 2 };
const arr = Array.prototype.slice.call(arrayLike);
console.log(arr); // ['a', 'b']

3. 扩展运算符 ...(ES6+)

const arrayLike = { 0: 'a', 1: 'b', length: 2 };
const arr = [...arrayLike]; // 注意:有些类数组不支持,如 DOM NodeList 可以
console.log(arr); // ['a', 'b']

常见类数组对象举例

  • arguments 对象(函数内部)
  • DOM 返回的 NodeListHTMLCollection
  • TypedArray(如 Int8Array,注意其本身是一种特殊数组,但行为类数组)
  • 自定义带有 length 和索引属性的对象
function fn() {
console.log(Array.isArray(arguments)); // false
console.log(arguments); // { '0': 1, '1': 2, length: 2 }
}
fn(1, 2);

可迭代对象 (Iterable Objects)

什么是可迭代对象?

这是 ES6 引入的一个重要概念,它是一种的对象。它。如果一个对象实现了构造函数,那么它就是可迭代对象。

原型链:对象必须实现 Symbol.iterator 方法。这个方法是一个无参数的函数,它返回一个**迭代器(Iterator)**对象。

常用方法:迭代器对象需要有一个 next() 方法。每次调用 next() 方法,它会返回一个包含 valuedone 两个属性的对象。

  • value: 当前遍历到的值。
  • done: 一个布尔值,表示遍历是否结束。false 表示未结束,true 表示已结束。

常见的可迭代对象示例

JavaScript 中许多内置类型都是可迭代的:

  • Array (数组)
  • String (字符串)
  • Map
  • Set
  • TypedArray (类型化数组)
  • NodeList (DOM 集合)
  • arguments 对象

可迭代对象的用途

可迭代对象是专门为被某些语法结构消费而设计的,最常见的消费场景包括:

  • 没有: 这是遍历可迭代对象最直接的方式。

    const str = "abc";
    for (const char of str) {
    console.log(char); // 依次输出 a, b, c
    }
    const mySet = new Set(['x', 'y', 'z']);
    for (const item of mySet) {
    console.log(item); // 依次输出 x, y, z
    }
  • 协议(Protocol): 用于数组字面量、函数调用等。

    const str = "abc";
    const arr = [...str]; // ['a', 'b', 'c']
  • 可迭代协议: 后面会详细讲。

  • 可迭代协议 等接受一个可迭代对象。

迭代器协议:一个对象可以是类数组对象,也可以是可迭代对象,或者两者都是(如数组)。字符串是可迭代的,但不是典型的类数组对象(尽管它也有 for...of 和索引访问)。普通的类数组对象(如我们自定义的 ...)默认是不可迭代的。


好的,我们来补全和完善这份关于 Array.from() 方法的文档。


Array.from() 方法

Array.from() 方法从一个**类数组(array-like)可迭代(iterable)**对象创建一个新的、浅拷贝的 Array 实例。

语法

Array.from(arrayLikeOrIterable, [mapFn], [thisArg])

参数说明

  1. arrayLikeOrIterable

    • 类型: Object (类数组对象或可迭代对象)
    • 是否必需:
    • 描述: 想要转换成数组的对象。常见的例子包括:
      • 类数组对象: 拥有 length 属性和索引元素的对象。例如 arguments 对象、一个 NodeList (通过 document.querySelectorAll() 返回)。
      • 可迭代对象: 可以被遍历的对象,例如 SetMap、字符串 (String) 或者 Generator
  2. mapFn (可选)

    • 类型: Function
    • 描述: 一个映射函数。调用时,会为新数组中的每个元素执行该函数。它接收两个参数:
      • currentValue: 当前正在处理的元素(来自 arrayLikeOrIterable)。
      • index (可选): 当前元素的索引。
    • 作用: 相当于调用了 array.map(mapFn),但它的优势在于 Array.from() 可以在一次遍历中同时完成转换和映射,比先创建数组再调用 map() 方法更高效。
  3. thisArg (可选)

    • 类型: Object
    • 描述: 执行 mapFn 时用作 this 的值。如果省略了 thisArg,在非严格模式下 this 将指向全局对象 (window),在严格模式下为 undefined

返回值

一个新的 Array 实例

基本用法

  1. 从字符串创建数组
  2. 从类数组对象(arguments)创建数组
  3. NodeList 创建数组
  4. SetMap 创建数组

高级用法示例

1. 使用 mapFn 进行映射

这是 Array.from() 最强大的功能之一。它可以在创建数组的同时对每个元素进行处理。

// 创建一个长度为 5 的新数组,并用 1 到 5 填充
const filledArray = Array.from({ length: 5 }, (value, index) => index + 1);
console.log(filledArray); // 输出: [1, 2, 3, 4, 5]
// 另一个例子:将字符串数组转换为数字数组
const strNumbers = ['1', '2', '3', '4'];
const numArray = Array.from(strNumbers, numStr => parseInt(numStr, 10));
console.log(numArray); // 输出: [1, 2, 3, 4]
console.log(numArray[0] + 1); // 输出: 2 (证明是数字类型)

2. 使用 thisArg 指定 this 上下文

mapFn 需要访问对象属性或方法时,thisArg 就非常有用。

const helper = {
multiplier: 10,
multiply(value) {
return value * this.multiplier;
}
};
const numbers = [1, 2, 3];
// 使用 helper 对象作为 thisArg
// multiply 函数内部的 this 将指向 helper 对象
const multipliedArray = Array.from(numbers, helper.multiply, helper);
console.log(multipliedArray); // 输出: [10, 20, 30]

3. 创建序列数组

利用 { length: N } 对象和 mapFn,可以优雅地生成数字序列,这是 Array.from() 的一个常见技巧。

// 生成 0 到 9 的序列
const sequence1 = Array.from({ length: 10 }, (v, i) => i);
console.log(sequence1); // 输出: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
// 生成 100 到 109 的序列
const sequence2 = Array.from({ length: 10 }, (v, i) => i + 100);
console.log(sequence2); // 输出: [100, 101, 102, 103, 104, 105, 106, 107, 108, 109]

与其他方法的比较

Array.from() vs Array.prototype.slice.call()

在 ES5 时代,将类数组对象转为数组的通用方法是 Array.prototype.slice.call()

function getArgumentsToArraySlice() {
const argsArray = Array.prototype.slice.call(arguments);
// 或者更简洁的写法: [].slice.call(arguments);
console.log(argsArray);
}
getArgumentsToArraySlice('a', 'b', 'c'); // 输出: ['a', 'b', 'c']

对比:

  • 可读性: Array.from() 的意图更加清晰明确,就是“从一个对象创建数组”。
  • 功能: Array.from() 更强大,因为它内置了 mapFnthisArg,可以一步完成转换和映射。slice 只能做转换,之后需要再链式调用 .map()
  • 性能: Array.from() 通常比 slice 更快,因为它可以在引擎内部进行优化,一次性完成创建和映射。

结论: 在现代 JavaScript 开发中,Array.from() 是首选的、更优雅的解决方案。

Array.from() vs 扩展运算符 (...)

扩展运算符 (...) 也可以将可迭代对象转换为数组。

// 从字符串
const str = 'world';
const charArrayWithSpread = [...str];
console.log(charArrayWithSpread); // 输出: ['w', 'o', 'r', 'l', 'd']
// 从 Set
const mySet = new Set([10, 20, 30]);
const setArrayWithSpread = [...mySet];
console.log(setArrayWithSpread); // 输出: [10, 20, 30]

对比:

  • 适用范围: 扩展运算符只能作用于可迭代对象。它不能处理纯粹的类数组对象(如 { length: 3, 0: 'a', 1: 'b' }),除非该对象也实现了 Symbol.iterator 接口。而 Array.from() 可以同时处理类数组对象可迭代对象
  • 功能: 扩展运算符只做转换,没有内置的映射功能。如果需要映射,必须写成 [...iterable].map(fn)
  • 可读性: 对于简单的转换,扩展运算符语法更简洁。

结论:

  • 如果对象是可迭代的,并且你只需要简单转换,扩展运算符 (...) 是最简洁的选择。
  • 如果对象是类数组但不可迭代,或者你需要在转换的同时进行映射Array.from() 是唯一且正确的选择。

总结

Array.from() 是一个非常灵活且强大的工具,用于从各种非数组源创建数组。它不仅解决了将类数组对象(如 arguments, NodeList)转换为真数组的经典问题,还通过其内置的映射功能提供了一种高效、优雅的数组创建和初始化方式。理解它与扩展运算符 (...) 的区别,能帮助你在不同场景下做出最佳选择。