ES6中数组新增了哪些扩展
问题解析
ES6 对数组进行了大量增强,提供了更强大的数组操作能力。这个问题考察对 ES6 数组新特性的全面掌握程度,包括扩展运算符、新的静态方法、实例方法等。
核心概念
1. 扩展运算符(...)
- 复制数组
- 合并数组
- 解构赋值
- 与字符串/类数组的转换
2. 静态方法
Array.from():类数组/可迭代对象转数组Array.of():创建数组,解决Array()的参数歧义
3. 实例方法
copyWithin():数组内部复制find()/findIndex():查找元素fill():填充数组entries()/keys()/values():遍历器方法includes():判断是否包含某值flat()/flatMap():数组扁平化
4. 其他改进
- 数组空位处理规范化
- 排序稳定性保证
详细解答
1. 扩展运算符(...)
复制数组
const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // 浅拷贝
arr2.push(4);
console.log(arr1); // [1, 2, 3]
console.log(arr2); // [1, 2, 3, 4]
// 对比 ES5
const arr3 = arr1.concat();
const arr4 = arr1.slice();
合并数组
const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = [5, 6];
// ES6
const merged = [...arr1, ...arr2, ...arr3];
console.log(merged); // [1, 2, 3, 4, 5, 6]
// ES5
const mergedES5 = arr1.concat(arr2, arr3);
解构赋值
const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(rest); // [2, 3, 4, 5]
// 与解构结合提取部分元素
const [a, b, ...others] = ['a', 'b', 'c', 'd', 'e'];
console.log(others); // ['c', 'd', 'e']
// 注意:扩展运算符必须是最后一个参数
const [x, ...y, z] = [1, 2, 3, 4]; // SyntaxError
与字符串/类数组的转换
// 字符串转数组
const chars = [...'hello'];
console.log(chars); // ['h', 'e', 'l', 'l', 'o']
// 类数组转数组
const nodeList = document.querySelectorAll('div');
const nodeArray = [...nodeList];
// Set 转数组
const set = new Set([1, 2, 3, 3, 3]);
const unique = [...set];
console.log(unique); // [1, 2, 3]
// Map 转数组
const map = new Map([['a', 1], ['b', 2]]);
const mapArray = [...map];
console.log(mapArray); // [['a', 1], ['b', 2]]
2. Array.from()
将类数组对象或可迭代对象转为真正的数组。
// 类数组对象
const arrayLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
const arr = Array.from(arrayLike);
console.log(arr); // ['a', 'b', 'c']
// NodeList
const divs = Array.from(document.querySelectorAll('div'));
// 字符串
const chars = Array.from('hello'); // ['h', 'e', 'l', 'l', 'o']
// Set
const set = new Set([1, 2, 3]);
const setArr = Array.from(set); // [1, 2, 3]
第二个参数:mapFn
// 类似于 map 方法
const numbers = Array.from([1, 2, 3], x => x * 2);
console.log(numbers); // [2, 4, 6]
// 生成序列
const range = Array.from({ length: 5 }, (_, i) => i);
console.log(range); // [0, 1, 2, 3, 4]
// 替代 new Array().fill().map()
const squares = Array.from({ length: 5 }, (_, i) => i * i);
console.log(squares); // [0, 1, 4, 9, 16]
第三个参数:thisArg
const doubler = {
factor: 2,
multiply(x) {
return x * this.factor;
}
};
const numbers = [1, 2, 3];
const doubled = Array.from(numbers, doubler.multiply, doubler);
console.log(doubled); // [2, 4, 6]
3. Array.of()
解决 Array() 构造函数的参数歧义问题。
// Array() 的问题
Array(); // []
Array(3); // [empty × 3] - 创建长度为3的空数组
Array(3, 4, 5); // [3, 4, 5]
// Array.of() 行为一致
Array.of(); // []
Array.of(3); // [3]
Array.of(3, 4, 5); // [3, 4, 5]
// 使用场景:创建包含单个数字元素的数组
const single = Array.of(5); // [5]
const emptyLength5 = Array(5); // [empty × 5]
4. copyWithin()
在数组内部将指定位置的成员复制到其他位置(会覆盖原有成员)。
// 语法:arr.copyWithin(target, start = 0, end = this.length)
// 将索引3到结束的元素复制到索引0开始的位置
[1, 2, 3, 4, 5].copyWithin(0, 3);
// [4, 5, 3, 4, 5]
// 将索引3到4的元素复制到索引0开始的位置
[1, 2, 3, 4, 5].copyWithin(0, 3, 4);
// [4, 2, 3, 4, 5]
// 负数索引
[1, 2, 3, 4, 5].copyWithin(-2, -3, -1);
// [1, 2, 3, 3, 4]
// 实际应用:数组旋转
const rotate = (arr, k) => {
const n = arr.length;
k = k % n;
return [...arr.slice(-k), ...arr.slice(0, -k)];
};
5. find() 和 findIndex()
// find():返回第一个满足条件的元素
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];
const user = users.find(u => u.id === 2);
console.log(user); // { id: 2, name: 'Bob' }
// 找不到返回 undefined
const notFound = users.find(u => u.id === 999);
console.log(notFound); // undefined
// findIndex():返回第一个满足条件的索引
const index = users.findIndex(u => u.id === 2);
console.log(index); // 1
// 找不到返回 -1
const notFoundIndex = users.findIndex(u => u.id === 999);
console.log(notFoundIndex); // -1
// 对比 filter(返回所有匹配项)
const adults = users.filter(u => u.id > 1);
console.log(adults); // [{ id: 2, ... }, { id: 3, ... }]
6. fill()
用给定值填充数组。
// 填充整个数组
new Array(5).fill(0);
// [0, 0, 0, 0, 0]
// 指定起始和结束位置
[1, 2, 3, 4, 5].fill('x', 1, 3);
// [1, 'x', 'x', 4, 5]
// 填充对象时的引用问题
const arr = new Array(3).fill({});
arr[0].name = 'Alice';
console.log(arr); // [{ name: 'Alice' }, { name: 'Alice' }, { name: 'Alice' }]
// 三个元素引用同一个对象
// 正确做法:使用 Array.from()
const arr2 = Array.from({ length: 3 }, () => ({}));
arr2[0].name = 'Alice';
console.log(arr2); // [{ name: 'Alice' }, {}, {}]
7. entries()、keys()、values()
返回遍历器对象,可用 for...of 遍历。
const arr = ['a', 'b', 'c'];
// keys():索引遍历器
for (const index of arr.keys()) {
console.log(index); // 0, 1, 2
}
// values():值遍历器
for (const value of arr.values()) {
console.log(value); // 'a', 'b', 'c'
}
// entries():键值对遍历器
for (const [index, value] of arr.entries()) {
console.log(index, value);
// 0 'a'
// 1 'b'
// 2 'c'
}
// 使用解构和 entries 获取索引和值
arr.entries().forEach(([i, v]) => console.log(i, v));
8. includes()
判断数组是否包含给定值。
// 基本用法
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
// 从指定位置开始查找
[1, 2, 3, 2].includes(2, 2); // true(从索引2开始查找)
[1, 2, 3].includes(2, 3); // false
// 与 indexOf 的区别
// 1. includes 使用 SameValueZero 算法,可以正确判断 NaN
[NaN].includes(NaN); // true
[NaN].indexOf(NaN); // -1
// 2. includes 更简洁,不需要比较 -1
if (arr.includes(item)) { } // 清晰
if (arr.indexOf(item) !== -1) { } // 繁琐
// 3. includes 不适用于对象引用比较(与 indexOf 相同)
const obj = { a: 1 };
[{ a: 1 }].includes({ a: 1 }); // false
[obj].includes(obj); // true
9. flat() 和 flatMap()
flat() - 数组扁平化
// 基本扁平化
[1, 2, [3, 4]].flat();
// [1, 2, 3, 4]
// 指定深度
[1, [2, [3, [4]]]].flat(2);
// [1, 2, 3, [4]]
// 完全扁平化(Infinity)
[1, [2, [3, [4]]]].flat(Infinity);
// [1, 2, 3, 4]
// 移除空位
[1, 2, , 4, 5].flat();
// [1, 2, 4, 5]
// 实际应用:处理嵌套数据
const comments = [
{ id: 1, replies: [{ id: 2 }, { id: 3 }] },
{ id: 4, replies: [{ id: 5 }] }
];
const allReplies = comments.map(c => c.replies).flat();
// [{ id: 2 }, { id: 3 }, { id: 5 }]
flatMap() - map + flat(1)
// 等价于 arr.map(...).flat(1),但更高效
const sentences = ['Hello world', 'Good morning'];
const words = sentences.flatMap(s => s.split(' '));
// ['Hello', 'world', 'Good', 'morning']
// 对比
const words2 = sentences.map(s => s.split(' ')).flat();
// 结果相同,但 flatMap 只遍历一次
// 过滤并映射
const numbers = [1, 2, 3, 4];
const evensDoubled = numbers.flatMap(n => n % 2 === 0 ? [n * 2] : []);
// [4, 8](过滤奇数,偶数翻倍)
// 一对多映射
const duplicates = [1, 2, 3].flatMap(x => [x, x]);
// [1, 1, 2, 2, 3, 3]
10. 数组空位处理
ES6 规范化了数组空位的处理,将空位转为 undefined。
// ES5 中空位处理不一致
// forEach, map, filter, every, some 会跳过空位
// join, toString 会将空位视为 undefined 或空字符串
// ES6 中,空位转为 undefined
Array.from([, , ,]); // [undefined, undefined, undefined]
[...['a', , 'b']]; // ['a', undefined, 'b']
Array.of(...[, , ,]); // [undefined, undefined, undefined]
// 注意:部分旧方法仍跳过空位
[,'a'].forEach((x, i) => console.log(i)); // 只输出 1
// 使用 fill 处理空位
new Array(3).fill(0); // [0, 0, 0]
11. 排序稳定性
ES2019 规定 sort() 必须是稳定排序。
// 稳定排序:相等元素的相对顺序保持不变
const items = [
{ name: 'Edward', value: 21 },
{ name: 'Sharpe', value: 37 },
{ name: 'And', value: 45 },
{ name: 'The', value: -12 },
{ name: 'Magnetic', value: 21 },
{ name: 'Zeros', value: 37 }
];
// 按 value 排序
items.sort((a, b) => a.value - b.value);
// 稳定排序保证 value 相同的元素保持原有顺序
// Edward (21) 在 Magnetic (21) 之前
// Sharpe (37) 在 Zeros (37) 之前
深入理解
1. 扩展运算符的实现原理
扩展运算符内部使用迭代器协议(Iterator Protocol):
// 可迭代对象必须实现 Symbol.iterator
const iterable = {
[Symbol.iterator]() {
let i = 0;
return {
next() {
if (i < 3) {
return { value: i++, done: false };
}
return { done: true };
}
};
}
};
const arr = [...iterable];
console.log(arr); // [0, 1, 2]
2. Array.from 的性能优势
// 方式1:扩展运算符(需要实现迭代器)
const arr1 = [...set]; // 需要遍历迭代器
// 方式2:Array.from(针对类数组优化)
const arr2 = Array.from(set); // 可能更快,内部优化
// 方式3:解构赋值
const arr3 = Array.prototype.slice.call(arrayLike);
Array.from 针对类数组对象进行了优化,在某些场景下性能更好。
3. flatMap 的应用模式
// 模式1:过滤并映射
const files = ['a.txt', 'b.jpg', 'c.png', 'd.txt'];
const images = files.flatMap(f => f.endsWith('.jpg') || f.endsWith('.png') ? [f] : []);
// 模式2:一对多映射
const ranges = [[1, 3], [5, 7]];
const numbers = ranges.flatMap(([start, end]) => {
const result = [];
for (let i = start; i <= end; i++) result.push(i);
return result;
});
// [1, 2, 3, 5, 6, 7]
// 模式3:添加/删除元素
const updates = [
{ type: 'add', value: 1 },
{ type: 'remove', value: 2 },
{ type: 'add', value: 3 }
];
const applyUpdate = (arr, update) => {
if (update.type === 'add') return [update.value];
if (update.type === 'remove') {
const idx = arr.indexOf(update.value);
return idx > -1 ? [,] : []; // 返回空数组表示删除
}
};
4. 类数组对象的精确判断
// 类数组对象:有 length 属性且为非负整数
function isArrayLike(obj) {
if (!obj || typeof obj !== 'object') return false;
const length = obj.length;
return typeof length === 'number' &&
length >= 0 &&
length === Math.floor(length) &&
length < Number.MAX_SAFE_INTEGER;
}
// 例子
isArrayLike({ 0: 'a', 1: 'b', length: 2 }); // true
isArrayLike('abc'); // true(字符串有 length)
isArrayLike({ length: -1 }); // false
isArrayLike({ length: 1.5 }); // false
最佳实践
1. 数组复制
// 浅拷贝数组
const copy = [...original];
// 或
const copy2 = Array.from(original);
// 或
const copy3 = original.slice();
// 深拷贝(简单对象)
const deepCopy = JSON.parse(JSON.stringify(arr));
// 深拷贝(复杂场景使用库)
import _ from 'lodash';
const deepCopy2 = _.cloneDeep(arr);
2. 数组去重
// 使用 Set(最简单)
const unique = [...new Set(arr)];
// 对象数组去重
const uniqueById = Array.from(new Map(arr.map(item => [item.id, item])).values());
3. 生成序列
// 生成 0 到 n-1
const range = n => Array.from({ length: n }, (_, i) => i);
// 生成 start 到 end
const range2 = (start, end) => Array.from({ length: end - start }, (_, i) => start + i);
// 生成随机数组
const randomArray = (length, max) =>
Array.from({ length }, () => Math.floor(Math.random() * max));
4. 数组分组
// 使用 reduce 分组
const groupBy = (arr, key) =>
arr.reduce((acc, item) => {
const group = item[key];
acc[group] = acc[group] || [];
acc[group].push(item);
return acc;
}, {});
// 使用 flatMap 展开分组结果
const flattenGroups = grouped => Object.values(grouped).flat();
5. 链式操作
const result = arr
.filter(x => x > 0)
.map(x => x * 2)
.flatMap(x => [x, x + 1])
.slice(0, 10);
面试要点
-
扩展运算符与剩余参数的区别:
- 扩展运算符:
...在等号右边或函数调用时,展开数组 - 剩余参数:
...在等号左边或函数参数时,收集剩余元素
- 扩展运算符:
-
Array.from 与扩展运算符的选择:
- 类数组对象优先使用
Array.from - 可迭代对象两者皆可
- 需要映射时使用
Array.from(arr, mapFn)
- 类数组对象优先使用
-
find vs filter:
find:找到第一个匹配项就返回,性能更好filter:返回所有匹配项,返回数组
-
includes vs indexOf:
includes可以正确判断NaNincludes返回布尔值,更语义化
-
flatMap 的优势:
- 比
map().flat()更高效(只遍历一次) - 适合过滤+映射、一对多映射场景
- 比
-
数组空位的处理:
- ES6 新方法将空位视为
undefined - 旧方法(如
forEach)会跳过空位 - 建议避免使用空位,显式使用
undefined
- ES6 新方法将空位视为
-
排序稳定性:
- ES2019 起
sort()保证稳定 - 稳定排序在多字段排序时很重要
- ES2019 起