JavaScript 中的 Array.from 与可迭代对象/类数组对象的转换机制
字数 1006 2025-12-14 15:57:05
JavaScript 中的 Array.from 与可迭代对象/类数组对象的转换机制
一、描述
Array.from() 是 ES6 引入的静态方法,用于从两类对象创建新的数组实例:
- 可迭代对象(如 Set、Map、字符串、Generator 等)
- 类数组对象(具有 length 属性和索引元素的对象,如 arguments、NodeList 等)
该方法不仅完成转换,还支持映射函数和 this 绑定,是替代传统 Array.prototype.slice.call() 的现代方案。
二、核心原理与步骤分解
步骤1:基本语法解析
Array.from(arrayLike[, mapFn[, thisArg]])
arrayLike:要转换为数组的可迭代或类数组对象mapFn(可选):对每个元素执行的映射函数thisArg(可选):执行 mapFn 时的 this 值
步骤2:内部转换机制详解
情况A:处理可迭代对象
- 检查对象是否实现了
Symbol.iterator方法 - 调用迭代器获取迭代器对象
- 循环调用迭代器的
next()方法 - 将每次迭代的 value 存入新数组
- 遇到
done: true时停止
示例:
// Set 是可迭代对象
const set = new Set([1, 2, 3])
const arr = Array.from(set) // [1, 2, 3]
// 内部相当于:
const iterator = set[Symbol.iterator]()
const result = []
let next = iterator.next()
while (!next.done) {
result.push(next.value)
next = iterator.next()
}
情况B:处理类数组对象
- 检查对象是否有 length 属性(会被转换为数字)
- 遍历从 0 到 length-1 的索引
- 检查每个索引是否存在于对象中(使用 in 操作符)
- 将存在的元素按顺序放入新数组
示例:
// 类数组对象
const arrayLike = {
0: 'a',
1: 'b',
length: 2
}
const arr = Array.from(arrayLike) // ['a', 'b']
// 内部相当于:
const len = Number(arrayLike.length) || 0
const result = new Array(len)
for (let i = 0; i < len; i++) {
if (i in arrayLike) {
result[i] = arrayLike[i]
}
}
步骤3:映射函数处理流程
如果有 mapFn 参数,转换过程会包含映射步骤:
Array.from(arrayLike, (value, index) => {
// 对每个元素进行处理
return transformedValue
})
内部实现逻辑:
function arrayFrom(arrayLike, mapFn, thisArg) {
const C = this // 通常是 Array
const items = Object(arrayLike)
const len = Number(items.length)
if (len <= 0) return []
const A = isConstructor(C) ? new C(len) : new Array(len)
let k = 0
while (k < len) {
const kValue = items[k]
if (mapFn) {
A[k] = typeof thisArg === 'undefined'
? mapFn(kValue, k)
: mapFn.call(thisArg, kValue, k)
} else {
A[k] = kValue
}
k++
}
A.length = len
return A
}
步骤4:与扩展运算符的区别
// Array.from 可以处理类数组对象
const arrayLike = { 0: 'a', 1: 'b', length: 2 }
Array.from(arrayLike) // 正确:['a', 'b']
[...arrayLike] // 错误:TypeError
// Array.from 可以处理可迭代对象
const set = new Set([1, 2, 3])
Array.from(set) // 正确:[1, 2, 3]
[...set] // 正确:[1, 2, 3]
// Array.from 支持映射函数
Array.from([1, 2, 3], x => x * 2) // [2, 4, 6]
[...[1, 2, 3]].map(x => x * 2) // 需要两步
三、实际应用场景
场景1:DOM 集合转换
// 将 NodeList 转换为数组
const divs = document.querySelectorAll('div')
const divArray = Array.from(divs)
// 旧方法对比
const oldWay = Array.prototype.slice.call(divs)
场景2:arguments 对象处理
function sum() {
// 将 arguments 转换为数组
const args = Array.from(arguments)
return args.reduce((a, b) => a + b, 0)
}
场景3:字符串字符处理
// 正确处理 Unicode 字符
const str = 'hello😊'
const chars = Array.from(str) // ['h', 'e', 'l', 'l', 'o', '😊']
// 对比 split('') 会拆分表情符号
str.split('') // ['h', 'e', 'l', 'l', 'o', '�', '�']
场景4:数字范围生成
// 生成数字序列
Array.from({ length: 5 }, (_, i) => i) // [0, 1, 2, 3, 4]
Array.from({ length: 3 }, () => 0) // [0, 0, 0]
场景5:去重与数据转换
// 结合 Set 实现数组去重
const arr = [1, 2, 2, 3, 3, 3]
const unique = Array.from(new Set(arr)) // [1, 2, 3]
// Map 值提取
const map = new Map([['a', 1], ['b', 2]])
const values = Array.from(map.values()) // [1, 2]
四、性能考虑与注意事项
-
性能优化:对于已知长度的数组,可以预分配长度
// 高效创建大型数组 const largeArray = Array.from({ length: 1000000 }, (_, i) => i) -
稀疏数组处理:
const sparse = { length: 3 } // 没有索引属性 Array.from(sparse) // [undefined, undefined, undefined] -
this 绑定:
const mapper = { multiplier: 2, mapFn(x) { return x * this.multiplier } } Array.from([1, 2, 3], mapper.mapFn, mapper) // [2, 4, 6] -
错误处理:
Array.from(123) // [],数字有 length 属性但不可迭代 Array.from({}) // [],空对象 Array.from(null) // TypeError Array.from(undefined) // TypeError
五、polyfill 实现核心逻辑
if (!Array.from) {
Array.from = (function() {
const toStr = Object.prototype.toString
const isCallable = function(fn) {
return typeof fn === 'function' || toStr.call(fn) === '[object Function]'
}
const toInteger = function(value) {
const number = Number(value)
if (isNaN(number)) return 0
if (number === 0 || !isFinite(number)) return number
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number))
}
const maxSafeInteger = Math.pow(2, 53) - 1
const toLength = function(value) {
const len = toInteger(value)
return Math.min(Math.max(len, 0), maxSafeInteger)
}
return function from(arrayLike, mapFn, thisArg) {
const C = this
const items = Object(arrayLike)
if (arrayLike == null) {
throw new TypeError('Array.from requires an array-like object')
}
const mapFn = arguments.length > 1 ? arguments[1] : void undefined
let T
if (typeof mapFn !== 'undefined') {
if (!isCallable(mapFn)) {
throw new TypeError('Array.from: when provided, the second argument must be a function')
}
if (arguments.length > 2) {
T = arguments[2]
}
}
const len = toLength(items.length)
const A = isCallable(C) ? Object(new C(len)) : new Array(len)
let k = 0
while (k < len) {
const kValue = items[k]
if (mapFn) {
A[k] = typeof T === 'undefined'
? mapFn(kValue, k)
: mapFn.call(T, kValue, k)
} else {
A[k] = kValue
}
k++
}
A.length = len
return A
}
})()
}
六、总结
Array.from() 提供了统一的数组创建接口,能够:
- 正确处理可迭代对象和类数组对象
- 支持映射函数,避免额外的 map 调用
- 正确处理 Unicode 字符串
- 替代传统的
Array.prototype.slice.call()方法 - 提供更清晰的语义和更好的性能
理解其内部机制有助于在需要数组转换的场景中选择最合适的方法,并在处理类数组对象时避免常见的错误。