Skip to content

3-3ES6数组和对象解构 ​

本页对应 第三章JavaScript进阶,共 8 段视频,约 1 小时 4 分。

学习目标 ​

  1. 能使用 ES6 属性简写与方法简写改写给定对象代码,并说明属性名与变量名一致时可省略属性值、方法可省略冒号和 function 的条件。
  2. 能对一维数组和多维数组完成解构赋值,正确写出默认值、剩余参数、空位跳过等代码,并预测变量多值少、变量少值多时的执行结果。
  3. 能识别以小括号或中括号开头的语句需要加分号的场景,定位并修复因解构赋值导致的语法错误。
  4. 能对对象及对象数组完成解构,按属性名匹配提取值,并使用“旧变量名:新变量名”的语法解决变量命名冲突。
  5. 能对多级对象进行嵌套解构,并在函数形参位置直接解构传入对象的核心字段,完成后台返回数据中目标数据的提取。
  6. 能说明垃圾回收机制常见算法(如标记清除、引用计数)的基本思路与局限,并能判断简单代码中的对象是否可被回收。

知识点 ​

从筛选需求理解 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,标记清除法从全局对象根部出发判断对象是否可达。引用计数法要警惕循环引用一类问题,现代浏览器已不再使用;标记清除法及其改进算法是现代浏览器常见方案的总体思想。

课件图示 ​

课件第86页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 拓展-JS垃圾回收机制-

课件第87页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 拓展-JS垃圾回收机制-

课件第88页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 拓展-JS垃圾回收机制-

课件第17页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 1.4 垃圾回收机制 内

代码示例 ​

示例一: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] 两个空逗号才能拿到第三个值。

常见错误 ​

  1. 现象:filter 后原数组也变了;原因:回调里直接修改了元素或误用 map/forEach 思维;修法:只返回布尔条件,需要改值另建数组。
  2. 现象:filter(ele => { ele >= 30 }) 结果为空;原因:箭头函数带大括号却没有 return;修法:写成 ele => ele >= 30 或补 return。
  3. 现象:对象简写后取到 undefined;原因:属性名与变量名不一致却仍省略属性值;修法:仅在同名时简写,否则恢复 属性名: 变量名。
  4. 现象:对象解构拿不到值;原因:变量名与属性名不一致,或重命名时把新旧名字写反;修法:保持同名,重命名写成 旧属性名: 新变量名。
  5. 现象:数组解构交换变量报“不能在声明前使用”;原因:上一行缺少分号,下一行以 [ 开头被连写解析;修法:在交换语句前或上一行末补分号。
  6. 现象:多级对象解构报错或家庭成员取不到;原因:漏写中间层属性名及冒号,直接写内层对象;修法:按层级写成 family: { mother, father, sister }。
  7. 现象:形参解构重命名后仍用旧名;原因:接收参数时已完成解构,后续应使用新变量名;修法:形参写 { date: myDate } 后统一使用 myDate。

练习 ​

  1. 给定 [10, 20, 30, 40],用 filter 得到 [30, 40],再分别用普通函数和箭头函数各写一遍。
  2. 有变量 userName、age,生成对象并简写属性和方法;再解构出 userName 并重命名为 name。
  3. 用数组解构完成:交换两个变量、忽略第二个值、用剩余参数接收其余值。
  4. 给定“对象包含数组、数组包含对象”的新闻数据,写函数只接收并解构出 data,同时重命名为 myData。
  5. 判断并修正:const { family: mother, father } = obj 能否取出多级对象中的 mother、father?说明应如何改。

原视频对照 ​

#视频时长
1ES6中对象属性和方法简写07:18
2数组解构赋值13:21
3数组解构赋值变量和值不匹配的情况08:22
4多维数组解构赋值03:43
5对象解构赋值基本使用09:20
6解构变量重命名解决变量冲突06:18
7多级对象解构07:03
8多级对象解构案例09:14

小结 ​

  1. filter 的核心是“返回条件”,结果是新数组,不应依赖它修改原数组。
  2. 对象属性和方法简写的前提是名称一致;方法简写不能保留 function 和冒号。
  3. 数组解构按位置匹配,对象解构按属性名匹配,多级结构必须逐层对应。
  4. 变量多、值少时会出现 undefined;变量少、值多时可忽略或用剩余参数。
  5. 解构重命名统一记作“原名在前、新名在后、中间冒号”,形参解构也遵循此规则。
  6. 以 ( 或 [ 开头的语句要注意与上一行分隔,必要时补分号。
  7. 现代浏览器垃圾回收主要采用标记清除及其改进算法,引用计数存在循环引用回收问题。
  8. 处理复杂数据先看外层是对象还是数组,再按“外层对应、内层对应、按需取名”的顺序写解构。