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 返回的 NodeList 或 HTMLCollection
  • 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() 方法,它会返回一个包含 value 和 done 两个属性的对象。

  • 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() 返回)。
      • 可迭代对象: 可以被遍历的对象,例如 Set、Map、字符串 (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. 从 Set 和 Map 创建数组

高级用法示例

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() 更强大,因为它内置了 mapFn 和 thisArg,可以一步完成转换和映射。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)转换为真数组的经典问题,还通过其内置的映射功能提供了一种高效、优雅的数组创建和初始化方式。理解它与扩展运算符 (...) 的区别,能帮助你在不同场景下做出最佳选择。