主题
函数新增特性与数组新增特性(Day2 3/4)
本页对应 Day02_ES6,共 5 段视频,约 1 小时 2 分。
学习目标
- 能使用扩展运算符将数组展开为逗号分隔的参数序列,并说明其与 rest 参数在“展开”和“合并”方向上的区别。
- 能使用扩展运算符完成数组复制、多个数组合并,以及将类数组/可迭代对象转换为纯数组,并判断操作是否创建了新数组。
- 能在数组解构赋值中正确使用扩展运算符收集剩余元素,并说明其必须位于解构变量末尾的原因。
- 能区分
Array.of()与Array.from()的作用,分别用它们创建新数组、将类数组或可迭代对象转换为纯数组。 - 能使用
find()、findIndex()、fill()完成按条件查找元素、查找索引和填充/替换数组元素,并判断fill()是否会修改原数组。 - 能使用
flat()、flatMap()、includes()、at()完成数组扁平化、映射后扁平化、包含性判断和按索引(含负数索引)读取元素,并解释各自返回值特征。
知识点
扩展运算符:把数组拆成参数序列
为什么需要:当函数有多个形参,而我们手里只有一个数组时,直接传数组只会占用第一个形参,后面的形参都是 undefined。
怎么想:把数组“拆开”,变成逗号分隔的参数序列,再传给函数。
怎么做:
js
const names = [100, 200, 300, 400];
function func(n1, n2, n3, n4) {
console.log(n1, n2, n3, n4);
}
func(names); // n1 是数组,n2~n4 是 undefined
func(...names); // 100 200 300 400易错点:三个点要写在数组前;func(...names) 等价于 func(100,200,300,400),不是把数组当整体传入。
复制数组:别把“别名”当复制
为什么需要:const arr202 = arr201 只复制了地址,两个变量指向同一个数组,改一个另一个也变。
怎么想:要得到新数组,就让 [...arr201] 先把元素拆成参数序列,再放进新数组。
怎么做:
js
const arr201 = [100, 200, 300, 400, 500];
const arr202 = [...arr201];
arr202[2] = '刘姥姥';
console.log(arr201); // 不受影响易错点:直接赋值是引用传递;[...arr201] 是浅拷贝,元素若是对象,对象内部仍共享引用。
合并数组与类数组转纯数组
为什么需要:多个数组合并、querySelectorAll 得到的类数组想用数组方法,都需要转成纯数组。
怎么想:扩展运算符能把数组或可迭代对象拆开,再放进 [] 里。
怎么做:
js
const arr203 = [...names, ...arr201, '刘姥姥'];
const liBooks = document.querySelectorAll('.news li');
const arr204 = [...liBooks];
const arr205 = [...'hello'];易错点:不加点会把整个数组当元素;类数组、字符串等可迭代对象可以拆,普通对象不行。
rest 参数与解构赋值中的扩展
为什么需要:函数接收不定数量参数,或解构时把剩余值收集起来。
怎么想:rest 参数是“把参数序列合并到数组”,扩展运算符在调用时是“把数组拆成参数序列”,二者方向相反。
怎么做:
js
function func(...args) {
console.log(args);
}
func(1, 2, 3, 4, 5);
const [a1, a2, ...a3] = ['刘姥姥', '马姥姥', '司马姥姥', '欧阳姥姥', '爱新觉罗姥姥'];易错点:rest 参数只能放形参最后;解构中的 ... 也要放最后,否则不合理。
Array.of 与 Array.from
为什么需要:new Array(12) 会得到长度 12 的空数组,而不是元素 12;类数组转纯数组也需要专门方法。
怎么想:Array.of 让参数直接成为元素;Array.from 从可迭代对象或类数组创建新数组。
怎么做:
js
Array.of(12); // [12]
Array.of(1, 2, 3); // [1,2,3]
const liBooks = document.querySelectorAll('.news li');
const arr = Array.from(liBooks);易错点:Array.of(12) 与 new Array(12) 不同;Array.from 是浅拷贝。
find/findIndex/fill
为什么需要:查找满足条件的第一个元素或其索引;批量填充/替换数组值。
怎么想:find、findIndex 接收回调,返回 true 表示当前元素符合条件;fill 用固定值替换元素。
怎么做:
js
users.find(item => item.age >= 45);
users.findIndex(item => item.age >= 45);
users.fill(250);
users.fill(150, 1, 3); // 从索引1到3,顾头不顾尾易错点:find 返回元素本身,findIndex 返回索引;fill 是修改器方法,会改原数组。
flat/flatMap/includes/at
为什么需要:多维数组扁平化、判断包含、按索引读取(支持负数)。
怎么想:flat 默认只拆一层,可用 Infinity 彻底扁平;includes 返回布尔值;at(-1) 取倒数第一个。
怎么做:
js
nameList.flat(Infinity);
numbers.includes(300);
numbers.includes(300, 3);
numbers.at(-1);易错点:flat() 默认只拆一层;flatMap 等价于先 map 再深度为 1 的 flat;at 的负索引是倒数,不是普通下标。
扩展运算符:把数组拆成参数序列
为什么需要:当函数有多个形参,而我们手里只有一个数组时,直接传数组只会占住第一个形参,后面的形参拿不到值。
怎么想:扩展运算符 ... 写在数组前,相当于把数组“拆开”,变成逗号分隔的参数序列;它和 rest 参数方向相反,rest 是“合”,扩展是“拆”。
怎么做:
js
const numbers = [10, 20, 30, 40];
function func(n1, n2, n3, n4) {
console.log(n1, n2, n3, n4);
}
func(...numbers); // 10 20 30 40调试时先写 func(numbers),会发现只有 n1 得到整个数组,n2 到 n4 是 undefined;加上 ... 后四个形参分别拿到元素。
易错点:... 不是数组方法,不能写成 numbers...;它要写在数组或可迭代对象前面。直接量也可以:func(...["刘姥姥", "马姥姥", "欧阳姥姥"])。
用扩展运算符复制数组
为什么需要:const arr2 = arr1 只是把地址赋给另一个变量,两个变量指向同一个数组,改一个会影响另一个。
怎么想:要真正复制,需要创建一个新数组,再把原数组元素展开进去。
怎么做:
js
const arr1 = [100, 200, 300, 400, 500];
const arr2 = [...arr1];
arr2[2] = "刘姥姥";
console.log(arr1); // [100, 200, 300, 400, 500]
console.log(arr2); // [100, 200, "刘姥姥", 400, 500]调试时先写 const arr2 = arr1,修改 arr2[2] 后 arr1 也变,说明二者是同一份数据;改成 [...arr1] 后互不影响。
易错点:这是浅拷贝。元素如果是对象,对象内部仍可能共享引用;不要把它理解成深拷贝。
合并数组与类数组转纯数组
为什么需要:合并多个数组、把 querySelectorAll 得到的类数组变成真正数组,都是常见需求。
怎么想:扩展运算符可以把多个数组展开到新数组中;可迭代对象/类数组也能被展开。
怎么做:
js
const names = ["刘姥姥", "马姥姥"];
const arr201 = [100, 200, 300];
const merged = [...names, ...arr201, "欧阳姥姥"];
const liBooks = document.querySelectorAll(".news li");
const arr204 = [...liBooks]; // 纯数组调试时先写 [...names, arr201],会发现 arr201 被当成一个元素;两边都加 ... 才是合并。
易错点:类数组不是所有场景都能直接展开;优先用 Array.from 或扩展运算符处理可迭代对象。
Array.of 与 Array.from
为什么需要:new Array(12) 会创建长度为 12 的空数组,而不是 [12],容易误解。
怎么想:Array.of 让参数直接成为数组元素;Array.from 把可迭代对象或类数组转成纯数组。
怎么做:
js
Array.of(12); // [12]
Array.of(1, 2, 3); // [1, 2, 3]
const lis = document.querySelectorAll("li");
const arr = Array.from(lis);
const chars = Array.from("hello"); // ["h","e","l","l","o"]易错点:Array.of(12) 和 new Array(12) 结果不同;Array.from 是浅拷贝,不是深拷贝。
find / findIndex / fill
为什么需要:按条件查找第一个满足项,或批量替换数组元素。
怎么想:find 返回第一个满足条件的元素,findIndex 返回其索引;fill 用固定值替换元素。
怎么做:
js
const users = [{ name: "刘备", age: 40 }, { name: "诸葛亮", age: 46 }];
users.find(item => item.age >= 45); // 诸葛亮
users.findIndex(item => item.age >= 45); // 1
const nums = [1, 2, 3, 4];
nums.fill(250); // [250, 250, 250, 250]
nums.fill(150, 1, 3); // 从索引1到3,不含3调试时注意:find 的回调要返回布尔值;不写回调会报错。
易错点:fill 是修改器方法,会改原数组;结束索引“顾头不顾尾”。
flat / flatMap / includes / at
为什么需要:多维数组扁平化、判断包含、按索引读取(尤其负数索引)。
怎么想:flat(n) 按层级拉平,默认一层;flatMap 等价于先 map 再 flat(1);includes 返回布尔值;at 支持负数。
怎么做:
js
const list = [1, [2, [3, [4]]]];
list.flat(); // 只拉平一层
list.flat(Infinity); // 彻底扁平化
[1, 2, 3].includes(3); // true
[10, 20, 30].at(-1); // 30易错点:flat() 默认只拉平一层,不是无限层;flatMap 的扁平深度固定为一层;at(-1) 是倒数第一个,不存在 -0。
课堂画面





代码示例
示例一:扩展运算符的核心用法(转参数序列 / 复制 / 合并 / 类数组转纯数组)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>扩展运算符</title>
</head>
<body>
<ul class="news">
<li>刘备</li>
<li>关羽</li>
<li>张飞</li>
</ul>
<script>
/* 1. 将数组转为逗号分隔的参数序列 */
const numbers = [100, 200, 300, 400, 500];
function func(n1, n2, n3, n4) {
console.log(n1, n2, n3, n4);
}
func(numbers); // 100 undefined undefined undefined —— 整个数组只当第一个实参
func(...numbers); // 100 200 300 400 —— 相当于 func(100, 200, 300, 400)
/* 2. 复制数组:不加点是“起别名”,加点是真复制 */
const arr01 = [100, 200, 300, 400, 500];
const arr02 = arr01; // 只把地址给了 arr02,两个变量指向同一个数组
arr02[2] = '刘姥姥';
console.log(arr01); // [100, 200, '刘姥姥', 400, 500] 原数组被连带修改
const arr03 = [...arr01]; // 中括号创建新数组,再把 arr01 拆开塞进去
arr03[2] = '司马姥姥';
console.log(arr01); // 不受影响,arr01 与 arr03 是两个数组
/* 3. 合并数组 */
const names = ['刘姥姥', '马姥姥'];
const arr04 = [...names, ...arr01, '欧阳姥姥'];
console.log(arr04);
/* 4. 类数组(可迭代对象)转纯数组 */
const liBooks = document.querySelectorAll('.news li');
const arr05 = [...liBooks];
console.log(Array.isArray(arr05), arr05); // true,里面是 li 元素
const arr06 = [...'Hello'];
console.log(arr06); // ['H','e','l','l','o']
</script>
</body>
</html>要点解释与易错点
func(numbers)与func(...numbers)的区别是本节最关键的一行:不加...时数组是“一个实参”,形参n2~n4全部拿到undefined;加了...才是“把数组拆成一个个值”。- 扩展运算符是 rest 参数(
...args)的逆运算:rest 把多个参数合并成数组,...把数组拆成多个参数。 const arr02 = arr01不是复制,只是把地址(引用)交给另一个变量,改arr02等于改arr01。必须写[...arr01]才是真正复制一份新数组。...只能写在数组/可迭代对象的前面,写在后面(如arr...)是语法错误。querySelectorAll返回的是类数组(可迭代对象),不是纯数组,所以才能用[...liBooks]拆开后再装进新数组。
示例二:Array.of / Array.from 与解构赋值中的剩余元素
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Array.of / Array.from / 解构</title>
</head>
<body>
<ul class="news">
<li>刘备</li>
<li>关羽</li>
<li>张飞</li>
</ul>
<script>
/* 1. Array.of:参数个数 = 新数组元素个数 */
console.log(Array.of(12)); // [12] 一个元素
console.log(new Array(12)); // 长度 12 的空数组,不是 [12]
console.log(Array.of(12, 34, 56)); // [12, 34, 56]
console.log(Array.of(12, [1, 2, 3], '司马姥姥')); // 数组整体作为一个元素
/* 2. Array.from:可迭代对象 / 类数组 -> 纯数组 */
const liBooks = document.querySelectorAll('.news li');
const arr01 = Array.from(liBooks);
console.log(Array.isArray(arr01), arr01);
const arr02 = Array.from('Hello');
console.log(arr02); // ['H','e','l','l','o']
/* 3. 解构赋值中的剩余元素(rest 在左边,与扩展运算符互逆) */
const [a1, a2, ...a3] =
['刘姥姥', '马姥姥', '司马姥姥', '欧阳姥姥', '爱新觉罗姥姥'];
console.log(a1, a2, a3);
// 刘姥姥 马姥姥 ['司马姥姥', '欧阳姥姥', '爱新觉罗姥姥']
</script>
</body>
</html>要点解释与易错点
Array.of(12)得到[12],而new Array(12)得到的是长度为 12 的空数组——这正是Array.of相对new Array做出的改进:只有一个数字参数时不再被当成“长度”。Array.from与[...liBooks]效果相同(都是把类数组/可迭代对象转纯数组),实际开发中任选其一,不必纠结。Array.from('Hello')说明字符串也是可迭代对象,每个字符成为一个元素。- 解构中的
...a3必须放在最后,写[a1, ...a2, a3]不合理也不合法(后面的元素无处分配),这和 rest 参数必须放在形参最后是同一条规则。
示例三:数组实例新增方法 find / fill / flat / includes / at
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>数组实例方法</title>
</head>
<body>
<script>
const users = [
{ name: '刘备', age: 30 },
{ name: '关羽', age: 38 },
{ name: '诸葛亮', age: 45 },
{ name: '曹操', age: 50 }
];
/* 1. find / findIndex:回调返回 true 表示“符合条件”,只找第一个 */
console.log(users.find(item => item.age >= 45)); // 诸葛亮 这个对象
console.log(users.findIndex(item => item.age >= 45)); // 2
/* 2. fill:用固定值替换元素,注意它是【修改器方法】 */
const nums = [100, 200, 300, 400, 500];
nums.fill(250);
console.log(nums); // [250,250,250,250,250],原数组被改
nums.fill(150, 1, 3); // 从索引 1 到 3,顾头不顾尾
console.log(nums); // [250,150,150,250,250]
console.log(new Array(12).fill('OK')); // 12 个 'OK',常和 new Array 搭配
/* 3. flat / flatMap */
const nameList = [1, [2, 3], [4, [5, [6, 7]]]];
console.log(nameList.flat()); // 默认只拆一层
console.log(nameList.flat(2)); // 拆两层,里面还有数组
console.log(nameList.flat(Infinity)); // 彻底扁平化
console.log([1, 2, 3].flatMap(item => item + 100)); // 等价 map 后 flat(1)
/* 4. includes / at */
console.log(nums.includes(300)); // true
console.log(nums.includes(300, 3)); // false,从索引 3 开始找就找不到了
console.log(nums.at(1)); // 150
console.log(nums.at(-1)); // 250 倒数第一个
console.log(nums.at(-3)); // 150 倒数第三个
console.log(nums[-1]); // undefined,中括号不能这样用
</script>
</body>
</html>要点解释与易错点
find的回调形式和map/filter/forEach一致(元素、索引、数组本身),回调返回true即表示当前元素符合条件;find返回元素本身,findIndex返回索引,且只返回第一个匹配项。fill是修改器方法,会直接改掉调用它的数组本身,不是返回新数组;区间fill(值, 起始, 结束)是“顾头不顾尾”(包含起始、不包含结束)。flat()默认只拆一层(默认参数是 1),要彻底扁平化写flat(Infinity);flatMap等价于先map再flat(1),深度固定为一层。includes返回布尔值,第二个参数是起始查找位置,索引之前的元素不参与查找;at的亮点是支持负数索引(at(-1)取最后一个),而nums[-1]只会得到undefined。
常见错误
- 现象:
fn(nums)后第一个形参拿到整个数组,其余形参为undefined。→ 原因:数组被当作单个实参传入。→ 修法:改为fn(...nums)。 - 现象:
const b = a; b[0] = 1;后原数组a也变了。→ 原因:数组是引用类型,=只复制地址。→ 修法:用const b = [...a]或Array.from(a)做浅拷贝。 - 现象:
const [a, ...rest, b] = arr;报语法错误。→ 原因:解构中的 rest 元素必须位于末尾。→ 修法:改为const [a, b, ...rest] = arr;。 - 现象:
new Array(3)得到长度为 3 的空数组,不是[3]。→ 原因:new Array单数字参数有长度歧义。→ 修法:需要元素时用Array.of(3)。 - 现象:对
querySelectorAll结果直接调用map报错。→ 原因:NodeList是类数组,不是纯数组。→ 修法:先[...nodeList]或Array.from(nodeList)。 - 现象:
arr.fill(0)后原数组被改,误以为返回新数组。→ 原因:fill是修改器方法。→ 修法:保留原数组时用[...arr].fill(0),或明确接受原地修改。
练习
- 改错:
function sum(a, b, c) {};const nums = [1, 2, 3]; sum(nums);要求让a/b/c分别得到1/2/3。 - 用扩展运算符完成:真复制
[1, 2],与[3, 4]合并,并在末尾追加5;不得修改原数组。 - 给定
users,用find找第一个age >= 45的元素,用findIndex找其索引,用at(-1)取最后一项;写出关键代码。 - 将
document.querySelectorAll('li')转为纯数组,并分别用扩展运算符、Array.from两种写法实现。 - 对多层数组
[1, [2, [3, [4]]]]做彻底扁平化;再复制一份并用fill填充索引1到3(不含 3),要求原数组不变。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 扩展运算符-将数组转为参数序列 | 10:00 |
| 2 | 扩展运算符-将数组转为参数序列02 | 08:26 |
| 3 | 扩展运算符-将多个参数序列合并到数组中 | 05:42 |
| 4 | Array构造函数本身的属性方法 | 06:50 |
| 5 | 数组对象的属性方法 | 31:58 |
小结
- 扩展运算符负责“展开”,rest 参数负责“收集”,二者语义相反且 rest 必须在末尾。
- 数组复制、合并优先用扩展运算符生成新数组,避免引用赋值带来的联动修改。
Array.of解决new Array单数字参数歧义;Array.from适合类数组/可迭代对象转纯数组。find/findIndex依赖回调返回真值定位第一个匹配项,不传回调会报错。fill是修改器;flat默认只扁平一层;at支持负索引。- 类数组不等于数组,先转纯数组再调用数组高阶方法。
- 选用数组 API 前先确认是否修改原数组,避免副作用。