[...a]
[...a] 是 ES6 引入的扩展运算符(Spread Syntax) 结合数组字面量的用法,核心作用是将 “可迭代对象”(Iterable)展开为数组元素,快速实现数组的复制、合并、转换等操作。
它的底层依赖我们上一节讲的 Iterator(迭代器) —— 只有满足 “可迭代协议” 的对象(即有 Symbol.iterator 方法的对象),才能用 [...a] 展开。
一、核心语法与原理
语法格式
javascript
运行
const newArray = [...可迭代对象];
可迭代对象:必须是支持迭代的对象(如数组、字符串、Set、Map、Generator 对象等,参考上一节的「原生支持迭代的对象」)。- 原理:底层会自动获取可迭代对象的迭代器(调用
[Symbol.iterator]()),通过next()方法遍历所有元素,将这些元素按顺序存入新数组并返回。
二、常见使用场景
1. 复制数组(浅拷贝)
快速创建数组的副本,避免直接赋值导致的 “引用传递” 问题。
javascript
运行
const arr = [1, 2, 3];
const arrCopy = [...arr]; // 展开 arr 并创建新数组
console.log(arrCopy); // [1, 2, 3]
console.log(arr === arrCopy); // false(新数组,地址不同)
// 对比:直接赋值是引用传递
const arrRef = arr;
console.log(arr === arrRef); // true(同一数组)
注意:仅做浅拷贝—— 如果数组元素是对象 / 数组(引用类型),拷贝的是引用地址,修改副本中的引用类型元素会影响原数组。
javascript
运行
const arr = [{ name: 'a' }, 2, 3];
const arrCopy = [...arr];
arrCopy[0].name = 'b'; // 修改副本中的对象属性
console.log(arr[0].name); // 'b'(原数组也被修改)
2. 合并数组
无需使用 concat(),直接合并多个可迭代对象(数组、字符串等)。
javascript
运行
const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = [5];
// 合并多个数组
const merged = [...arr1, ...arr2, ...arr3];
console.log(merged); // [1, 2, 3, 4, 5]
// 合并数组与其他可迭代对象(如字符串)
const str = 'ab';
const merged2 = [...arr1, ...str];
console.log(merged2); // [1, 2, 'a', 'b']
3. 转换可迭代对象为数组
将字符串、Set、Map 等可迭代对象快速转为数组(比 Array.from() 更简洁)。
javascript
运行
// 1. 字符串 → 数组(遍历字符)
const str = 'hello';
const strArr = [...str];
console.log(strArr); // ['h', 'e', 'l', 'l', 'o']
// 2. Set → 数组(去重特性保留)
const set = new Set([1, 2, 2, 3]);
const setArr = [...set];
console.log(setArr); // [1, 2, 3](自动去重)
// 3. Map → 数组(遍历 entries,格式为 [key, value])
const map = new Map([['name', '张三'], ['age', 25]]);
const mapArr = [...map];
console.log(mapArr); // [['name', '张三'], ['age', 25]]
// 4. Generator 对象 → 数组
function* generator() {
yield 1;
yield 2;
yield 3;
}
const genArr = [...generator()];
console.log(genArr); // [1, 2, 3]
4. 函数参数展开(补充:非数组字面量场景)
扩展运算符还可用于函数参数,将数组展开为独立参数(与 [...a] 原理一致,都是基于迭代器):
javascript
运行
const nums = [1, 2, 3];
// 求和函数
function sum(a, b, c) {
return a + b + c;
}
// 展开数组为参数
console.log(sum(...nums)); // 6(等价于 sum(1, 2, 3))
5. 与解构赋值结合
在解构赋值中,扩展运算符可获取数组剩余元素(必须放在最后):
javascript
运行
const [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1(第一个元素)
console.log(rest); // [2, 3, 4](剩余元素组成的新数组)
// 错误:扩展运算符不能放在中间或开头
// const [...rest, last] = [1, 2, 3]; // SyntaxError
三、注意事项
1. 仅支持 “可迭代对象”
如果 a 不是可迭代对象(如普通对象、null、undefined),使用 [...a] 会报错:
javascript
运行
// 普通对象(默认不可迭代)
const obj = { name: '张三', age: 25 };
// const objArr = [...obj]; // TypeError: obj is not iterable
// 解决:如果要转换普通对象,需先转为可迭代对象(如 Object.entries())
const objArr = [...Object.entries(obj)];
console.log(objArr); // [['name', '张三'], ['age', 25]]
2. 浅拷贝限制
如场景 1 所述,[...a] 仅拷贝数组的 “表层元素”,引用类型元素仍共享引用。如果需要深拷贝,需结合 JSON.parse(JSON.stringify()) 或第三方库(如 Lodash 的 cloneDeep)。
3. 与 Array.from() 的区别
两者都能将可迭代对象转为数组,但适用场景有差异:
| 特性 | [...a] | Array.from(a) |
|---|---|---|
| 核心依赖 | 迭代器协议 | 迭代器协议 + 类数组对象(有 length 属性) |
| 支持类数组对象 | 不支持(如 { length: 3 }) | 支持(如 Array.from({ length: 3 }, (_, i) => i) → [0,1,2]) |
| 第二个参数(映射) | 不支持 | 支持(可直接对元素做映射,如 Array.from(str, c => c.toUpperCase())) |
| 简洁性 | 更简洁(数组字面量场景) | 功能更全(类数组 + 映射) |
示例:类数组对象的差异
javascript
运行
// 类数组对象(有 length,但不可迭代)
const likeArray = { length: 2, 0: 'a', 1: 'b' };
// [...likeArray] → TypeError: likeArray is not iterable
const arr1 = Array.from(likeArray);
console.log(arr1); // ['a', 'b'](正常转换)
4. 空值处理
如果 a 是 null 或 undefined,[...a] 会报错;如果 a 是空数组 / 空可迭代对象,返回空数组:
javascript
运行
// [...null] → TypeError
// [...undefined] → TypeError
const emptyArr = [...[]];
console.log(emptyArr); // []
const emptySet = [...new Set()];
console.log(emptySet); // []
四、总结
[...a] 是基于 Iterator 协议的实用语法糖,核心能力是将可迭代对象展开为数组,主要用于:
- 浅拷贝数组、合并数组;
- 转换字符串 / Set/Map 等为数组;
- 与解构赋值结合获取剩余元素。
它的优势是简洁直观,底层依赖迭代器,因此仅支持可迭代对象,且需注意浅拷贝的限制。在日常开发中,是处理数组和可迭代对象的高频用法。
Array.from
Array.from() 是 ES6 新增的数组静态方法,核心作用是将 “类数组对象” 或 “可迭代对象” 转换为真正的数组,同时支持对转换后的元素进行映射处理,灵活性比扩展运算符 [...a] 更强。
一、基本语法
javascript
运行
Array.from(arrayLikeOrIterable[, mapFn[, thisArg]])
参数说明
-
arrayLikeOrIterable(必选) :-
要转换的对象,支持两种类型:
- 类数组对象(Array-like Object):有
length属性,且属性名是 0、1、2... 数字索引(如arguments、DOM 集合、{ length: 3, 0: 'a', 1: 'b' }); - 可迭代对象(Iterable):符合迭代器协议的对象(如数组、字符串、Set、Map、Generator 对象,参考之前讲的 Iterator)。
- 类数组对象(Array-like Object):有
-
-
mapFn(可选) :- 映射函数,对转换过程中的每个元素进行处理,等价于
Array.from(xxx).map(mapFn),但性能更优(仅遍历一次)。 - 接收两个参数:当前元素
value、当前索引index。
- 映射函数,对转换过程中的每个元素进行处理,等价于
-
thisArg(可选) :- 绑定
mapFn中的this指向。
- 绑定
二、核心功能与示例
1. 转换类数组对象为数组
类数组对象的关键特征是「有 length 属性 + 数字索引」,但没有数组的原型方法(如 push、forEach),Array.from() 可直接将其转为真正的数组。
示例 1:转换 DOM 集合
javascript
运行
// 获取 DOM 集合(类数组对象)
const divs = document.querySelectorAll('div');
console.log(divs instanceof Array); // false(不是数组)
// 转为数组
const divArr = Array.from(divs);
console.log(divArr instanceof Array); // true(真正的数组)
divArr.forEach(div => console.log(div)); // 可调用数组方法
示例 2:转换自定义类数组对象
javascript
运行
// 类数组对象:有 length + 数字索引
const likeArray = {
length: 3,
0: 'a',
1: 'b',
2: 'c'
};
const arr = Array.from(likeArray);
console.log(arr); // ['a', 'b', 'c'](真正的数组)
// 注意:length 决定数组长度,未定义的索引会转为 undefined
const likeArray2 = { length: 4, 0: 1, 2: 3 };
console.log(Array.from(likeArray2)); // [1, undefined, 3, undefined]
示例 3:转换 arguments 对象
javascript
运行
// arguments 是函数内部的类数组对象
function sum() {
const args = Array.from(arguments); // 转为数组
return args.reduce((acc, cur) => acc + cur, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
2. 转换可迭代对象为数组
可迭代对象(数组、字符串、Set、Map、Generator 等)均可通过 Array.from() 转为数组,效果与扩展运算符 [...a] 类似,但支持映射函数。
示例 1:转换字符串(遍历字符)
javascript
运行
const str = 'hello';
const strArr = Array.from(str);
console.log(strArr); // ['h', 'e', 'l', 'l', 'o']
// 结合映射函数:转为大写
const upperArr = Array.from(str, c => c.toUpperCase());
console.log(upperArr); // ['H', 'E', 'L', 'L', 'O']
示例 2:转换 Set(自动去重)
javascript
运行
const set = new Set([1, 2, 2, 3, 4]);
const setArr = Array.from(set);
console.log(setArr); // [1, 2, 3, 4](去重后)
// 映射函数:每个元素乘 2
const doubleArr = Array.from(set, num => num * 2);
console.log(doubleArr); // [2, 4, 6, 8]
示例 3:转换 Generator 对象
javascript
运行
function* generator() {
yield 'x';
yield 'y';
yield 'z';
}
const genArr = Array.from(generator());
console.log(genArr); // ['x', 'y', 'z']
3. 生成指定长度的数组(结合映射函数)
利用「类数组对象 + 映射函数」,可快速生成有规律的数组(无需先创建空数组再填充)。
示例 1:生成 0~9 的数组
javascript
运行
// { length: 10 } 是类数组对象,映射函数返回索引
const arr = Array.from({ length: 10 }, (_, index) => index);
console.log(arr); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
示例 2:生成随机数数组
javascript
运行
// 生成 5 个 0~100 的随机整数
const randomArr = Array.from({ length: 5 }, () => Math.floor(Math.random() * 101));
console.log(randomArr); // 如 [32, 7, 89, 15, 67]
示例 3:填充相同值(避免引用类型共享问题)
javascript
运行
// 错误:Array(5).fill({}) 会让所有元素指向同一个对象
// 正确:Array.from 每次调用映射函数,创建新对象
const objArr = Array.from({ length: 3 }, () => ({ name: 'default' }));
objArr[0].name = 'a';
console.log(objArr); // [{ name: 'a' }, { name: 'default' }, { name: 'default' }]
4. 处理数组的浅拷贝与去重
示例 1:浅拷贝数组
javascript
运行
const original = [1, { age: 20 }, 3];
const copy = Array.from(original);
console.log(copy); // [1, { age: 20 }, 3]
console.log(copy === original); // false(新数组)
copy[1].age = 25;
console.log(original[1].age); // 25(引用类型共享引用,浅拷贝特性)
示例 2:数组去重(结合 Set)
javascript
运行
const duplicateArr = [1, 2, 2, 3, 3, 3];
// 先转为 Set 去重,再转为数组
const uniqueArr = Array.from(new Set(duplicateArr));
console.log(uniqueArr); // [1, 2, 3]
三、关键区别:Array.from () vs 扩展运算符 [...a]
两者都能转换可迭代对象为数组,但 Array.from() 功能更全面,适用场景更广,核心差异如下:
| 特性 | Array.from(arrayLike/iterable) | [...iterable] |
|---|---|---|
| 支持类数组对象 | ✅(有 length 即可,无需迭代器) | ❌(仅支持可迭代对象,类数组会报错) |
| 支持映射函数 | ✅(直接对元素处理,遍历一次) | ❌(需额外调用 map(),遍历两次) |
| 第二个参数作用 | 映射函数(value, index) | 无(仅展开元素) |
| 简洁性 | 稍繁琐 | 更简洁(可迭代对象场景) |
| 空值处理 | Array.from(null/undefined) 报错 | [...null/undefined] 报错 |
示例:类数组对象的差异
javascript
运行
const likeArray = { length: 2, 0: 'x', 1: 'y' };
// Array.from 支持类数组
console.log(Array.from(likeArray)); // ['x', 'y']
// 扩展运算符不支持类数组(报错)
// console.log([...likeArray]); // TypeError: likeArray is not iterable
示例:映射函数的差异
javascript
运行
const str = 'abc';
// Array.from 直接映射(一次遍历)
const upper1 = Array.from(str, c => c.toUpperCase());
// 扩展运算符 + map(两次遍历)
const upper2 = [...str].map(c => c.toUpperCase());
console.log(upper1, upper2); // 均为 ['A', 'B', 'C'],但 Array.from 性能更优
四、注意事项
-
浅拷贝限制:与扩展运算符类似,
Array.from()仅做浅拷贝,引用类型元素仍共享引用,深拷贝需额外处理。 -
类数组对象的
length优先级:转换类数组时,数组长度由length属性决定,而非实际存在的数字索引(如{ length: 5, 0: 1 }转换后是[1, undefined, undefined, undefined, undefined])。 -
mapFn的参数:映射函数接收(value, index)两个参数,若无需value,可使用下划线_占位(约定俗成的空参数标识)。 -
不支持普通对象:普通对象(无
length或非数字索引)默认无法直接转换,需先转为类数组或可迭代对象(如Array.from(Object.entries(obj))):javascript
运行
const obj = { name: '张三', age: 25 }; // 转换为 [key, value] 形式的数组 const objArr = Array.from(Object.entries(obj)); console.log(objArr); // [['name', '张三'], ['age', 25]]
五、总结
Array.from() 是功能强大的数组转换工具,核心优势是:
- 同时支持「类数组对象」和「可迭代对象」;
- 内置映射函数,兼顾转换与处理,性能更优;
- 可快速生成有规律的数组(如指定长度、随机数、对象数组)。
日常开发中,适合用于 DOM 集合转换、类数组处理、数组生成、浅拷贝 + 去重等场景,是比扩展运算符更灵活的数组转换方案。