主题
3-3ES6数组和对象解构
本页对应 第三章JavaScript进阶,共 8 段视频,约 1 小时 4 分。
学习目标
- 能使用 ES6 属性简写与方法简写改写给定对象代码,并说明属性名与变量名一致时可省略属性值、方法可省略冒号和
function的条件。 - 能对一维数组和多维数组完成解构赋值,正确写出默认值、剩余参数、空位跳过等代码,并预测变量多值少、变量少值多时的执行结果。
- 能识别以小括号或中括号开头的语句需要加分号的场景,定位并修复因解构赋值导致的语法错误。
- 能对对象及对象数组完成解构,按属性名匹配提取值,并使用“旧变量名:新变量名”的语法解决变量命名冲突。
- 能对多级对象进行嵌套解构,并在函数形参位置直接解构传入对象的核心字段,完成后台返回数据中目标数据的提取。
- 能说明垃圾回收机制常见算法(如标记清除、引用计数)的基本思路与局限,并能判断简单代码中的对象是否可被回收。
知识点
从筛选需求理解 filter
为什么需要:数组里经常要“挑出符合条件的元素”,例如从 [10,20,30,40] 中取出大于等于 30 的项。用 for 循环也能做,但还要手动建新数组、写 push,代码啰嗦。filter() 直接把“筛选条件”写成回调返回值,更适合综合案例中的数据过滤。
怎么想:filter() 会创建一个新数组,新数组中的元素是回调返回真值的元素。它不影响原数组。回调参数常见为 ele(当前元素)和 index(索引),真正决定“留不留”的是 return 后面的条件。
怎么做:本节笔记代码如下,保留原代码:
html
<body>
<script>
// filter 筛选数组元素
const arr = [10, 20, 30, 40]
// const newArr = arr.filter(function (ele, index) {
// // console.log(ele)
// // console.log(index)
// return 筛选条件
// return ele >= 30
// // return ele + 30 都是真的所以都选出来了
// })
const newArr = arr.filter(ele => ele >= 30)
console.log(newArr)
</script>
</body>易错点:第一,回调要返回筛选条件,不写 return 会得到 undefined,元素不会被保留。第二,return ele + 30 不是比较条件,数值非 0 时会被当成真值,所以可能“全选出来”;若结果是 0 又会被过滤。第三,filter 返回新数组,原数组不变,不要误以为原数组被修改。
综合案例中的数据准备:对象属性和方法简写
为什么需要:综合案例常要把表单值组装成对象,例如用户名、密码。若属性名和变量名相同,还写 userName: userName 会很重复。ES6 允许对象属性简写,方法也可以简写。
怎么想:对象属性的简写条件是“属性名和属性值变量名一致”,此时只写属性名。方法简写是把 hi: function(){} 写成 hi(){},省掉冒号和 function。转写稿中通过点击按钮获取表单值,打印对象验证是否生成成功。
怎么做:可写成:
js
const userName = document.querySelector('#userName').value
const password = document.querySelector('#password').value
const obj = {
userName,
password,
hi() {
console.log('hi')
}
}
console.log(obj)易错点:属性简写必须属性名和变量名一致;方法之间要用逗号隔开;方法简写不要漏掉小括号和大括号。转写稿调试时打印对象,能看到 userName、password 和 hi 方法。
数组解构与对象解构:从综合数据中取值
为什么需要:综合案例里常见 arr[0]、user.userName 这种重复写法,变量多时不易读懂。解构赋值可以把数组中的值或对象中的属性取出来,赋给其他变量。
怎么想:数组解构按位置赋值,左侧必须用中括号;对象解构按属性名匹配,左侧必须用大括号,变量名要和属性名一致。若变量名冲突,可用“旧名: 新名”重命名。
怎么做:数组解构:const [a, b, c] = [1, 2, 3]。对象解构:const { userName, age: userAge } = user。变量多至少会得到 undefined,可设默认值;变量少值多可用剩余参数 ...rest;按需跳过可用空逗号。转写稿还演示了交换变量:[x, y] = [y, x]。
易错点:数组解构左侧别写成大括号;对象解构左侧别写成中括号;重命名是“变量名: 新变量名”,不要写反;解构变量不要和外部变量重名,否则报“已被声明”。转写稿中交换变量报错,是因为上一行缺少分号,导致以中括号开头的语句和上一行连在一起;小括号开头、中括号开头是需特别注意加分号的两种情况。
多级对象解构案例:从返回数据中拿 data
为什么需要:Ajax 返回的数据常是“对象里包数组、数组里包对象”,例如 { code, message, data },真正要渲染的是 data。每次都写 msg.data 也能用,但在函数参数中直接解构更简洁。
怎么想:先看右侧最外层是对象还是数组,左侧写同样的结构。对象里再套对象时,要保留外层属性名并加冒号,再写内层大括号。转写稿中佩奇案例就是 family: { mother, father, sister }。
怎么做:先取 data:const { data } = msg。函数形参接收时直接解构:function render({ data: myData }) { console.log(myData) }。这里 data 是原属性名,myData 是新变量名,避免和函数内其他 data 冲突。
易错点:形参解构要带大括号,否则只是普通形参;重命名方向不能反;多级对象解构时外层属性名不能删掉,否则结构对不上。转写稿调试时打印 data 能得到对象数组,打印 myData 也能拿到核心数据,说明形参接收的同时完成了对象解构。
本节说明与主题边界
本节笔记主题是“拓展-垃圾回收机制算法”,核心内容是堆栈空间分配区别、引用计数法、标记清除法。需要说明:随附转写稿讲的是 ES6 对象属性方法简写、数组/对象解构赋值等内容,与本节垃圾回收主题不一致,无法用来补本节的推导和调试过程;本节笔记也没有附图片和代码,因此下面不虚构配图或代码,只按笔记原有知识点扩写。
为什么需要理解堆栈与垃圾回收
为什么需要:程序运行要使用内存,而内存有限。理解栈和堆由谁分配、谁释放,才能明白 JavaScript 中哪些内存由操作系统自动处理,哪些复杂数据需要垃圾回收机制介入。
怎么想:先区分“栈空间”和“堆空间”。栈由操作系统自动分配释放,存放函数参数值、局部变量等,基本数据类型放到栈里;堆一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收,复杂数据类型放到堆里。
怎么做:学习垃圾回收算法时,不要把它理解成“删除变量名”,而要理解成“判断一块堆内存是否还会被使用”。JS 中复杂数据类型放在堆中,垃圾回收主要关注这些堆内存何时可以释放。
易错点:不要把栈的自动释放和垃圾回收混为一谈。栈由操作系统自动分配释放;堆一般由程序员分配释放,程序员不释放时由垃圾回收机制回收。也不要把“变量被重新赋值”简单等同于“对象立刻被回收”,回收要看算法如何判断对象是否还需要使用。
栈与堆的空间分配区别
为什么需要:垃圾回收讨论的对象主要在堆中,因此必须先明确栈和堆的职责差异。
怎么想:栈空间的管理规则比较固定,函数调用、参数、局部变量等由操作系统自动处理;堆空间更动态,复杂数据类型大小和生命周期不固定,需要由程序员或垃圾回收机制管理。
怎么做:按笔记记住两点:第一,栈由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面;第二,堆一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收,复杂数据类型放到堆里面。
易错点:不要把所有内存都交给垃圾回收。栈有操作系统的自动管理;堆才是一般由程序员分配释放、必要时由垃圾回收机制回收的区域。理解这一点,后面看引用计数和标记清除时,才知道它们主要是在判断堆中对象是否还能继续使用。
引用计数法
为什么需要:需要一种算法判断对象“内存不再使用”。引用计数法的思路是看一个对象是否有指向它的引用,没有引用了就回收对象。笔记指出 IE 采用这种引用计数算法。
怎么想:给对象维护一个“被引用次数”。有引用就加,引用减少就减,次数变成 0 就说明没有引用指向它,可以释放内存。
怎么做:按笔记中的算法步骤:第一,跟踪记录被引用的次数;第二,如果被引用了一次,就记录次数 1,多次引用会累加;第三,如果减少一个引用就减 1;第四,如果引用次数是 0,则释放内存。
易错点:引用计数法的判断依据是“引用次数”,不是“对象是否还能从根部到达”。因此要特别注意循环引用一类情况:对象之间互相引用时,引用次数可能不为 0,但对象整体可能已经不再被外部使用。笔记也明确说,现代浏览器已经不再使用引用计数算法了,所以学习它主要是理解思想,而不是把它当成现代浏览器的主要回收方案。
标记清除法
为什么需要:现代浏览器已经不再使用引用计数算法,现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想一致。标记清除法把“不再使用的对象”定义为“无法达到的对象”,用可达性来判断是否需要回收。
怎么想:不要只盯着某个对象有没有被直接引用,而要从“根部”出发看能不能到达它。在 JS 中,根部就是全局对象。定时扫描内存中的对象,凡是能从根部到达的对象,都是还需要使用的;那些无法由根部出发触及到的对象,被标记为不再使用,稍后进行回收。
怎么做:按笔记核心步骤理解:第一,将“不再使用的对象”定义为“无法达到的对象”;第二,从根部,也就是 JS 中的全局对象出发,定时扫描内存中的对象;第三,能从根部到达的对象都还需要使用;第四,无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
易错点:第一,不要把这套算法理解成“看变量名是否还存在”,它的核心是“从根部是否可达”。第二,标记为不再使用不等于立刻当场删除,笔记写的是“稍后进行回收”。第三,现代浏览器使用的是基于标记清除算法的某些改进算法,总体思想一致,不要把引用计数法当成现代浏览器的主要算法。理解“可达则保留、不可达则标记回收”,就抓住了标记清除法的核心。
小结
本节要掌握三个层次:栈由操作系统自动分配释放,基本数据类型放到栈里;堆一般由程序员分配释放,复杂数据类型放到堆里,程序员不释放时由垃圾回收机制回收;引用计数法看引用次数是否为 0,标记清除法从全局对象根部出发判断对象是否可达。引用计数法要警惕循环引用一类问题,现代浏览器已不再使用;标记清除法及其改进算法是现代浏览器常见方案的总体思想。
课件图示




代码示例
示例一:filter 筛选数组
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>filter 筛选数组</title>
</head>
<body>
<script>
// filter 筛选数组元素
const arr = [10, 20, 30, 40]
// 完整写法
// const newArr = arr.filter(function (ele, index) {
// // console.log(ele)
// // console.log(index)
// // return 筛选条件
// return ele >= 30
// // return ele + 30 都是真的所以都选出来了
// })
// 箭头函数简写
const newArr = arr.filter(ele => ele >= 30)
console.log(newArr) // [30, 40]
console.log(arr) // [10, 20, 30, 40]
</script>
</body>
</html>关键行
arr.filter(ele => ele >= 30):filter()创建一个新数组,新数组中的元素是符合条件的所有元素,返回的是ele >= 30为true的元素。console.log(arr):原数组不变,filter不影响原数组,主要使用场景是筛选数组符合条件的元素。
易错点
- 回调里必须
return筛选条件;写成return ele + 30也是真值,会把所有元素都选出来(转写稿注释已标注)。 - 箭头函数简写时不写大括号,若写成
ele => { ele >= 30 }则没有返回值,结果为空数组。 - 忘记接收返回值:
arr.filter(...)不赋值给新变量,原数组不会发生任何变化。
示例二:ES6 对象属性与方法简写
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ES6 对象属性和方法简写</title>
</head>
<body>
<input type="text" class="userName">
<input type="password" class="password">
<button>点击按钮</button>
<script>
const btn = document.querySelector('button')
btn.addEventListener('click', function () {
// 一定要写到点击里面才能获取最新的值
const userName = document.querySelector('.userName').value
const password = document.querySelector('.password').value
// 属性名和属性值相同,只写属性名即可
const obj = {
userName,
password,
// 方法简写:去掉冒号和 function
sayHi() {
console.log('hi')
}
}
console.log(obj)
obj.sayHi()
})
</script>
</body>
</html>关键行
const obj = { userName, password }:属性名userName和属性值变量userName相同,只写属性名即可,等价于{ userName: userName, password: password }。sayHi() { console.log('hi') }:方法简写,把冒号和function全部省略,直接写方法名() {}。
易错点
- 属性简写的前提是属性名和属性值(变量名)必须一模一样,不同名时不能简写。
- 获取表单值必须写在点击事件的回调函数里面,写在外面拿到的是页面初始值。
- 属性与方法之间、多个属性之间必须用逗号隔开,漏写逗号会报语法错误。
- 方法简写后仍要通过
obj.sayHi()调用,不能写成obj.sayHi(那样只是取到函数本身)。
示例三:解构赋值综合(数组解构 + 对象解构 + 多级对象解构)
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>解构赋值</title>
</head>
<body>
<script>
/* ---------- 1. 数组解构 ---------- */
const arr = [1, 2, 3]
const [a, b, c] = arr
console.log(a, b, c) // 1 2 3
// 交换两个变量的值
let x = 1
let y = 2;
[x, y] = [y, x]
console.log(x, y) // 2 1
// 变量多、值少:给默认值
const [m, n, o, p = '三星'] = ['小米手机', '华为手机', '苹果手机']
console.log(m, n, o, p)
// 变量少、值多:剩余参数
const [q, ...rest] = ['小米', '华为', '苹果']
console.log(q, rest)
// 按需导入:用空逗号忽略某些值
const [, , phone] = ['小米', '华为', '苹果']
console.log(phone) // 苹果
// 多维数组解构:右侧是数组,左侧对应位置也写数组
const [first, [second, third]] = ['小米', ['华为', '苹果']]
console.log(first, second, third)
/* ---------- 2. 对象解构 ---------- */
const user = { userName: '小明', age: 18, gender: '男' }
const { userName, age } = user
console.log(userName, age)
// 重命名:变量名在前,冒号,新变量名在后
const { userName: uName } = user
console.log(uName)
/* ---------- 3. 多级对象解构(Ajax 返回数据格式) ---------- */
const msg = {
code: 200,
message: '获取新闻列表成功',
data: [
{ id: 1, title: '新闻1', count: 100 },
{ id: 2, title: '新闻2', count: 200 }
]
}
// 只要 data
const { data } = msg
console.log(data)
// 形参接收数据的同时进行对象解构,并把 data 重命名为 myData
function render({ data: myData }) {
console.log(myData)
}
render(msg)
</script>
</body>
</html>关键行
const [a, b, c] = arr:数组解构按位置依次赋值,等价于const a = arr[0]、const b = arr[1]、const c = arr[2],一句话完成。[x, y] = [y, x]:先算右侧得到[1, 2],再解构给左侧,不需要第三个变量就完成交换。const [m, n, o, p = '三星'] = [...]:变量多、值少时多余变量为undefined,可给默认值防止undefined。const [q, ...rest] = [...]:剩余参数把后面所有值收到一个数组里,解决变量少、值多的问题。const [, , phone] = [...]:按需导入,用空的逗号跳过不要的值,注意位置是按序号对应的。const [first, [second, third]] = [...]:多维数组解构,右侧是数组,左侧对应位置也写数组,一层层对应。const { userName, age } = user:对象解构按属性名匹配,与顺序无关。const { userName: uName } = user:重命名,变量名在前、冒号、新变量名在后,之后用新变量名uName。function render({ data: myData }):形参接收数据的同时做对象解构,只取data并重命名为myData。
易错点
- 数组解构左侧必须是数组(中括号不能落下),对象解构左侧必须是大括号。
- 对象解构时变量名必须与属性名一致,写成
const { name } = user得到的是undefined(属性名是userName)。 - 重命名方向不能写反:是
属性名: 新变量名,新变量名在后面;写反了就拿不到值。 - 变量名不能和外部已有变量冲突,否则报
has already been declared,用重命名解决。 - 分号问题:
[x, y] = [y, x]以中括号开头,上一行let y = 2后面必须加分号,否则会和上一行连成2[x, y]报错;同理,以小括号开头的立即执行函数前也要加分号。 - 多级对象解构时,外层属性名要保留,如
const { family: { mother, father, sister } } = peppa,直接删掉family会报错。 - 数组解构跳过值时逗号不能省,
const [, , phone]两个空逗号才能拿到第三个值。
常见错误
- 现象:
filter后原数组也变了;原因:回调里直接修改了元素或误用map/forEach思维;修法:只返回布尔条件,需要改值另建数组。 - 现象:
filter(ele => { ele >= 30 })结果为空;原因:箭头函数带大括号却没有return;修法:写成ele => ele >= 30或补return。 - 现象:对象简写后取到
undefined;原因:属性名与变量名不一致却仍省略属性值;修法:仅在同名时简写,否则恢复属性名: 变量名。 - 现象:对象解构拿不到值;原因:变量名与属性名不一致,或重命名时把新旧名字写反;修法:保持同名,重命名写成
旧属性名: 新变量名。 - 现象:数组解构交换变量报“不能在声明前使用”;原因:上一行缺少分号,下一行以
[开头被连写解析;修法:在交换语句前或上一行末补分号。 - 现象:多级对象解构报错或家庭成员取不到;原因:漏写中间层属性名及冒号,直接写内层对象;修法:按层级写成
family: { mother, father, sister }。 - 现象:形参解构重命名后仍用旧名;原因:接收参数时已完成解构,后续应使用新变量名;修法:形参写
{ date: myDate }后统一使用myDate。
练习
- 给定
[10, 20, 30, 40],用filter得到[30, 40],再分别用普通函数和箭头函数各写一遍。 - 有变量
userName、age,生成对象并简写属性和方法;再解构出userName并重命名为name。 - 用数组解构完成:交换两个变量、忽略第二个值、用剩余参数接收其余值。
- 给定“对象包含数组、数组包含对象”的新闻数据,写函数只接收并解构出
data,同时重命名为myData。 - 判断并修正:
const { family: mother, father } = obj能否取出多级对象中的mother、father?说明应如何改。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | ES6中对象属性和方法简写 | 07:18 |
| 2 | 数组解构赋值 | 13:21 |
| 3 | 数组解构赋值变量和值不匹配的情况 | 08:22 |
| 4 | 多维数组解构赋值 | 03:43 |
| 5 | 对象解构赋值基本使用 | 09:20 |
| 6 | 解构变量重命名解决变量冲突 | 06:18 |
| 7 | 多级对象解构 | 07:03 |
| 8 | 多级对象解构案例 | 09:14 |
小结
filter的核心是“返回条件”,结果是新数组,不应依赖它修改原数组。- 对象属性和方法简写的前提是名称一致;方法简写不能保留
function和冒号。 - 数组解构按位置匹配,对象解构按属性名匹配,多级结构必须逐层对应。
- 变量多、值少时会出现
undefined;变量少、值多时可忽略或用剩余参数。 - 解构重命名统一记作“原名在前、新名在后、中间冒号”,形参解构也遵循此规则。
- 以
(或[开头的语句要注意与上一行分隔,必要时补分号。 - 现代浏览器垃圾回收主要采用标记清除及其改进算法,引用计数存在循环引用回收问题。
- 处理复杂数据先看外层是对象还是数组,再按“外层对应、内层对应、按需取名”的顺序写解构。