类数组对象(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() | true | false |
| 遍历方式 | 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(字符串)MapSetTypedArray(类型化数组)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])参数说明
-
arrayLikeOrIterable- 类型:
Object(类数组对象或可迭代对象) - 是否必需: 是
- 描述: 想要转换成数组的对象。常见的例子包括:
- 类数组对象: 拥有
length属性和索引元素的对象。例如arguments对象、一个NodeList(通过document.querySelectorAll()返回)。 - 可迭代对象: 可以被遍历的对象,例如
Set、Map、字符串 (String) 或者Generator。
- 类数组对象: 拥有
- 类型:
-
mapFn(可选)- 类型:
Function - 描述: 一个映射函数。调用时,会为新数组中的每个元素执行该函数。它接收两个参数:
currentValue: 当前正在处理的元素(来自arrayLikeOrIterable)。index(可选): 当前元素的索引。
- 作用: 相当于调用了
array.map(mapFn),但它的优势在于Array.from()可以在一次遍历中同时完成转换和映射,比先创建数组再调用map()方法更高效。
- 类型:
-
thisArg(可选)- 类型:
Object - 描述: 执行
mapFn时用作this的值。如果省略了thisArg,在非严格模式下this将指向全局对象 (window),在严格模式下为undefined。
- 类型:
返回值
一个新的 Array 实例。
基本用法
- 从字符串创建数组
- 从类数组对象(
arguments)创建数组 - 从
NodeList创建数组 - 从
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']
// 从 Setconst 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)转换为真数组的经典问题,还通过其内置的映射功能提供了一种高效、优雅的数组创建和初始化方式。理解它与扩展运算符 (...) 的区别,能帮助你在不同场景下做出最佳选择。