Skip to content

3-2展开运算符和箭头函数 ​

本页对应 第三章JavaScript进阶,共 5 段视频,约 48 分钟。

学习目标 ​

  • 能够说明 arguments 对象只存在于函数内部、以伪数组形式保存所有实参,并用传统 for 循环完成不定数量实参的求和。
  • 能够使用剩余参数 ...args 接收多余实参,并对比说明其与 arguments 在真数组、forEach 使用及箭头函数支持上的区别。
  • 能够使用展开运算符展开数组,完成数组最大值/最小值求解与数组合并,并解释其与剩余参数“展开”和“收集”的区别。
  • 能够将普通匿名函数改写为箭头函数,并正确应用省略小括号、省略大括号与 return、返回对象时加小括号等简写规则。
  • 能够分析箭头函数中 this 沿用上一层作用域的特点,并判断在 DOM 事件回调或定时器等场景中是否应使用箭头函数。

知识点 ​

函数提升:先声明,后调用更灵活 ​

为什么需要:函数声明和变量一样,在代码执行前会经历“提升”。理解它,才能解释为什么有的函数能在写之前调用,有的却报错。

怎么想:函数提升提升到当前作用域最前面;只提升声明,不提升调用;函数表达式不存在提升现象;函数提升能让声明和调用顺序更灵活。

怎么做:观察下面代码,fn()能执行,是因为函数声明被提升;fun()在赋值前调用会报错,因为var fun只提升变量声明,此时值是undefined。

html
<body>
  <script>
    function fn() {
      console.log('函数提升')
    }
    var fun
    // 函数提升
    // 1.函数提升提升到当前作用域最前面
    // 2.函数提升只提升声明,不提升调用
    // 3.函数表达式不存在提升的现象
    // 4.函数提升能够使函数的声明调用更灵活
    fn()
    fun()
    fun = function () {
      console.log('函数表达式不存在提升')
    }
    fun()
  </script>
</body>

易错点:不要把“函数表达式”当“函数声明”。fun()写在赋值前会报TypeError,调试时先看调用位置是否早于赋值。

arguments对象:了解即可,能拿全部实参 ​

为什么需要:封装求和函数时,实参个数不确定,形参没法提前写全。arguments能拿到调用时传入的所有实参。

怎么想:它是函数内部内置的伪数组,只存在于函数中;有length,能通过下标和for循环访问,但没有forEach等真数组方法。

怎么做:用传统for循环累加。转写稿里调试时发现:传1,2得到3,传1,2,3得到6,传1,2,3,4得到10。

html
<body>
  <script>
    // arguments对象获取所有实参
    function sum() {
      // 1. arguments 只存在于函数中 伪数组
      // 2. arguments 可以得到传递过来的所有实参 [1, 2]
      // console.log(arguments)
      let sum = 0
      for (let i = 0; i < arguments.length; i++) {
        sum += arguments[i]
      }
      console.log(sum)
    }
    sum(1, 2)
    sum(1, 2, 3)
    sum(1, 2, 3, 4)
    // console.log(arguments) 外面无法使用
  </script>
</body>

易错点:arguments是伪数组,不能直接forEach;函数外访问会报arguments is not defined。开发中更提倡剩余参数。

剩余参数:把多余实参收成真数组 ​

为什么需要:arguments是伪数组,使用受限;剩余参数能把“剩余”的实参收集成真数组,还能解决形参和实参个数不匹配的问题。

怎么想:...other写在最末形参前,表示“后面不确定的实参都收进other”。other是真数组,可用forEach。

怎么做:求和时用forEach累加;只固定前两个参数时,可写function sum(x, y, ...other)。

html
<body>
  <script>
    // 剩余参数获得多余的实参 返回真数组
    // function sum(x, y, ...other) {
    //   console.log(x, y, other)
    // }
    // sum(1, 2)
    // sum(1, 2, 3)
    // sum(1, 2, 3, 4)

    // 1. 求和效果
    function sum(...other) {
      // console.log(other)  // [1, 2, 3, 4]
      let sum = 0
      other.forEach(function (ele) {
        sum += ele
      })
      console.log(sum)
    }
    sum(1, 2)
    sum(1, 2, 3)
    sum(1, 2, 3, 4)
  </script>
</body>

易错点:...是语法符号,必须放在最末形参前;使用变量时不要再写...。与arguments区别:剩余参数是真数组;箭头函数不支持arguments,但支持剩余参数。

展开运算符:把数组拆成一个个元素 ​

为什么需要:Math.max不能直接接收数组,数组合并也不想得到二维数组。展开运算符能把数组“拆散”。

怎么想:剩余参数是“凝聚”,展开运算符是“拆散”。展开不修改原数组。

怎么做:console.log(...arr)展开输出;Math.max(...arr)求最大值;[...arr1, ...arr2]合并数组。转写稿调试:Math.max(arr)得到NaN,改成Math.max(...arr)得到3。

html
<body>
  <script>
    // 展开运算符:将一个数组/对象进行展开,我们先讲展开数组
    // 1. 基本使用, 不修改原数组
    const arr = [1, 2, 3]
    console.log(...arr)
    console.log(arr)

    //2. 使用场景: 求数组最大值/最小值,数组合并
    // console.log(Math.max(1, 3, 8))
    console.log(Math.max(...arr))  // 3
    console.log(Math.min(...arr))  // 1
    const arr1 = [1, 2, 3]
    const arr2 = [4, 5, 6]
    // console.log([arr1, arr2])
    console.log([...arr1, ...arr2])
  </script>
</body>

易错点:Math.max(arr)不是展开,会得到NaN;[arr1, arr2]是二维数组,合并要用[...arr1, ...arr2]。

箭头函数:更简洁的匿名函数写法 ​

为什么需要:匿名函数场景很多,箭头函数比函数表达式更简洁。

怎么想:把function替换成=>。细节:只有一个形参可省小括号;函数体只有一句可省大括号并自动返回;返回对象要用小括号包裹;箭头函数没有arguments,但有剩余参数。

怎么做:

html
<body>
  <script>
    // 箭头函数
    // 1. 基本语法
    // const fn = function () {
    //   console.log('我是函数表达式')
    // }
    // fn()
    // const fn = () => {
    //   console.log('我是箭头函数')
    // }
    // fn()

    // 2. 细节使用
    // 2.1 如果只有一个形参则可以省略小括号,其余个数不能省略,如果没有参数则写空的小括号
    // const sum = (x) => {
    //   console.log(x + x)
    // }
    // sum(2)
    // const sum = x => {
    //   console.log(x + x)
    // }
    // sum(2)

    // 2.2 如果函数体只有一句代码,则可以省略大括号,这句代码就是返回值(省略return)
    // const sum = x => {
    //   return x + x
    // }
    // const sum = x => x + x
    // console.log(sum(5))

    // 2.3 如果返回的是一个对象,则需要用小括号把对象包裹起来
    // const fn = function() {
    //   return {name: '佩奇'}
    // }
    // const fn = () => ({ name: '佩奇' })
    // console.log(fn())

    // 2.4 箭头函数里面没有 arguments,但是有剩余参数
    const fn = (...other) => {
      // console.log(arguments)
      console.log(other)
    }
    fn(1, 2)
  </script>
</body>

易错点:返回对象写成() => { name: '佩奇' }会把大括号当函数体;箭头函数属于表达式函数,不存在函数提升;剩余参数不能省小括号。

箭头函数中的this:沿用上一层作用域 ​

为什么需要:普通函数this由调用者决定;箭头函数本身没有this,它沿用上一层作用域的this,用错场景会拿不到按钮。

怎么想:先找箭头函数外层作用域的this是谁。转写稿调试:btn1用箭头函数后this.style.color报错,因为this是window;定时器里普通函数this是window,改成箭头函数后沿用外层按钮的this,能重新启用按钮。

怎么做:

html
<body>
  <button class="btn1">点击</button>
  <button class="btn2">5秒后启用</button>
  <script>
    // 1. 以前this的指向:  指向调用者
    // console.log(this)  // window
    // // 普通函数
    // function fn() {
    //   console.log(this)  // window
    // }
    // window.fn()
    // // 对象方法里面的this
    // const obj = {
    //   name: 'andy',
    //   sayHi: function () {
    //     console.log(this)  // obj
    //   }
    // }
    // obj.sayHi()

    // 2. 箭头函数的中this指向-沿用上一层作用域的this 
    const fn = () => {
      console.log(this)  // window
    }
    fn()
    // const obj = {
    //   name: 'andy',
    //   sayHi: () => {
    //     console.log(this)  // window
    //   }
    // }
    // obj.sayHi()

    const obj = {
      name: 'andy',
      sayHi: function () {
        const fun = () => {
          console.log(this)  // obj 
        }
        fun()
      }
    }
    obj.sayHi()

    // 3. 我们可以根据需求来选择是否使用箭头函数 this
    // document.querySelector('.btn1').addEventListener('click', function () {
    //   this.style.color = 'red'
    // })
    document.querySelector('.btn1').addEventListener('click', () => {
      // this.style.color = 'red'
      // 此处不能用 this 指向 Window不是 按钮了
      document.querySelector('.btn1').style.color = 'red'
    })

    document.querySelector('.btn2').addEventListener('click', function () {
      this.disabled = true
      // setTimeout(function () {
      //   console.log(this) //  定时器里面的this 指向 window 
      //   this.disabled = false
      // }, 5000)

      setTimeout(() => {
        console.log(this) //  
        this.disabled = false
      }, 5000)
    })
  </script>
</body>

易错点:DOM事件里想用this指向按钮,就别用箭头函数;定时器里想沿用外层this,可用箭头函数。判断标准永远是“外层作用域的this是谁”。

ES6对象简写:属性与方法更紧凑 ​

为什么需要:当属性名和变量名一致、方法还是匿名函数时,重复写很啰嗦。

怎么想:属性名和属性值一致时只写属性名;对象方法可省略: function。

怎么做:

html
<body>
  用户名: <input type="text" class="username"> <br>
  密 码: <input type="password" class="password"> <br>
  <button>点击</button>
  <script>
    // ES6对象属性和方法的简写
    // 1. 对象属性的简写 (点击按钮生成对象)
    document.querySelector('button').addEventListener('click', function () {
      const username = document.querySelector('.username').value
      const password = document.querySelector('.password').value
      // const obj = {
      //   username: username,
      //   password: password
      // }
      // 属性名和属性值相同的时候,可以只写属性名
      // 2. 对象方法的简写
      // const obj = {
      //   username,
      //   password,
      //   sayHi: function() {
      //     console.log('hi~')
      //   }
      // }
      const obj = {
        username,
        password,
        sayHi() {
          console.log('hi~')
        }
      }
      console.log(obj)
    })
  </script>
</body>

易错点:简写属性要求变量名和属性名一致;方法简写不是箭头函数,方法里的this仍按调用对象判断。

解构赋值到底解决什么问题 ​

为什么需要:从数组或对象里取数据再逐个赋给变量,代码会重复、啰嗦。比如要先 arr[0]、arr[1],再写变量声明。
怎么想:解构就是“把结构拆开”,右侧提供数组或对象,左侧按相同结构写变量,让取值动作在一行里完成。
怎么做:数组解构看“位置”,对象解构看“属性名”。
易错点:不要把数组解构和对象解构的匹配规则混了:数组按顺序,对象按属性名。

数组解构:按位置依次赋值 ​

为什么需要:数组取值依赖下标,多个变量赋值时重复写下标不简洁。
怎么想:右侧数组的值按位置赋给左侧变量,变量顺序对应值的位置。
怎么做:

html
<body>
  <script>
    // 数组解构
    // 1. 基本语法
    const [a, b, c] = [1, 2, 3]
    console.log(a)
    console.log(b)
    console.log(c)

    // 2. 典型的使用场景  交换2个变量的值
    let x = 1
    let y = 2;
    [y, x] = [x, y]
    console.log(x, y);

    // 3. js 2个特殊情况需要加分号
    // 3.1 如果是小括号开头的则需要加分号
    (function () { })();
    (function () { })();
    // 3.2 如果是中括号开头的则需要加分号

  </script>
</body>

易错点:以 ( 或 [ 开头的语句前要注意分号,否则可能和上一行连在一起被解析。交换变量时 [y, x] = [x, y] 就是利用数组解构完成交换。

变量和值不匹配:默认值、剩余、按需忽略 ​

为什么需要:实际数据可能多一个、少一个,不能假设左右完全一样。
怎么想:变量多值少,缺的得到 undefined;想避免 undefined 就给默认值;值多变量少,可以用剩余元素收集,或空位跳过。
怎么做:

html
<body>
  <script>
    // 数组解构变量和值不匹配的情况

    // 1. 变量多,值少的情况
    // const [a, b, c, d] = ['小米', '华为', '苹果']
    // console.log(a)
    // console.log(b)
    // console.log(c)
    // console.log(d) // undefined

    // 2. 防止undefined传值,可以设置默认值
    // const [a, b, c, d = '三星'] = ['小米', '华为', '苹果']
    // console.log(a)
    // console.log(b)
    // console.log(c)
    // console.log(d)

    // 3. 变量少,值多的情况
    // const [a, b] = ['小米', '华为', '苹果']
    // console.log(a)
    // console.log(b)

    // 4. 利用剩余参数解决变量少值多的情况
    // const [a, ...b] = ['小米', '华为', '苹果']
    // console.log(a)
    // console.log(b)

    // 5. 按需导入,忽略某些值
    const [a, , c, d] = ['小米', '华为', '苹果', 'vivo']
    console.log(a)
    console.log(c)
    console.log(d)
  </script>
</body>

易错点:默认值不是“值少就一定用”,而是对应位置为 undefined 时才生效;...b 要放在最后,它收集剩余值。转写稿里把“真数组”误写成“帧数组”、把“伪数组”误写成“尾数组”,这里纠正:解构剩余得到的 b 是数组;它和展开运算符方向相反——解构里的 ... 是收集,展开运算符是把数组拆开。

对象解构:按属性名取值与重命名 ​

为什么需要:对象属性多,逐个 user.username、user.age 取值重复。
怎么想:左侧变量名默认要和对象属性名一致;不一致时用“属性名: 新变量名”重命名。
怎么做:

html
<body>
  <script>
    const username = 'andy'
    const user = {
      username: '小明',
      age: 18
    }
    // 1. 对象解构赋值基本使用
    // const { username, age, gender } = user
    // console.log(username)  // 小明
    // console.log(age)  // 18
    // console.log(gender)  // undefined

    // 1.1 要求变量名和属性名必须一致
    // 1.2 如果变量名和属性名不一致,则默认为 undefined
    // 1.3 变量名不要和外面的变量名冲突,否则会报错

    // 2. 更改解构变量名(重命名)  变量名: 新变量名
    // const { username: uname, age } = user
    // console.log(uname)  // 小明
    // console.log(age)  // 18

    // 3. 对象数组解构
    const arr = [
      {
        username: '小明',
        age: 18
      }
    ]

    const [{ username: uname, age }] = arr
    console.log(uname)
    console.log(age)
  </script>
</body>

易错点:外部已经有 const username = 'andy',再写 const { username } = user 会变量名冲突,所以要重命名成 uname;对象里没有的属性解构出来是 undefined。

多维解构与函数参数解构 ​

为什么需要:后台返回的数据常常是“对象里套数组、数组里套对象”,只取需要的一层能减少中间变量。
怎么想:按数据结构逐层对齐;函数参数也可以直接解构,调用时把整个对象传进去。
怎么做:

html
<body>
  <script>
    // 1. 这是后台传递过来的数据
    const msg = {
      "code": 200,
      "msg": "获取新闻列表成功",
      "data": [
        {
          "id": 1,
          "title": "5G商用自己,三大运用商收入下降",
          "count": 58
        },
        {
          "id": 2,
          "title": "国际媒体头条速览",
          "count": 56
        },
        {
          "id": 3,
          "title": "乌克兰和俄罗斯持续冲突",
          "count": 1669
        },

      ]
    }

    // 需求1: 请将以上msg对象  采用对象解构的方式 只选出  data 方面后面使用渲染页面
    // const { data } = msg
    // console.log(data)
    // 需求2: 上面msg是后台传递过来的数据,我们需要把data选出当做参数传递给 函数
    // const { data } = msg
    // msg 虽然很多属性,但是我们利用解构只要 data值
    function render({ data }) {
      // const { data } = arr
      // 我们只要 data 数据
      // 内部处理
      console.log(data)

    }
    render(msg)

    // 需求3, 为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData
    function render({ data: myData }) {
      // 要求将 获取过来的 data数据 更名为 myData
      // 内部处理
      console.log(myData)

    }
    render(msg)

  </script>
</body>

易错点:函数参数解构 render({ data }) 表示调用时传入对象,并从对象里取 data;要改名就用 { data: myData }。演示时两个同名 render 不要同时保留,实际代码里保留一种写法即可。

课件图示 ​

课件第54页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 练习 独立完成数组解构赋

课件第49页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 3. 解构赋值 解构赋值

课件第53页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 总结 总结 1. 解构赋

课件第56页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 3.1 数组解构 解构赋

课件第62页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 3.2 对象解构 对象解

课件第67页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 练习 独立完成对象数组解

课件第69页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 3.2 对象解构 解构赋

课件第73页:多一句没有,少一句不行,用最短时间,教会最实用的技术! 步骤 渲染商品列表案例

代码示例 ​

示例一:函数参数(arguments / 剩余参数)与展开运算符 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>函数参数与展开运算符</title>
</head>
<body>
  <script>
    // 1. arguments:函数内部内置的伪数组,包含调用时传入的所有实参
    function sum1() {
      let sum = 0
      for (let i = 0; i < arguments.length; i++) {
        sum += arguments[i]
      }
      console.log('arguments求和:', sum)
    }
    sum1(1, 2)        // 3
    sum1(1, 2, 3)     // 6
    sum1(1, 2, 3, 4)  // 10

    // 2. 剩余参数:把多余的实参收集成一个“真数组”
    function sum2(...other) {
      let sum = 0
      other.forEach(function (ele) {
        sum += ele
      })
      console.log('剩余参数求和:', sum)
    }
    sum2(1, 2)        // 3
    sum2(1, 2, 3, 4)  // 10

    // 前两个参数固定,后面数量不定
    function fn(x, y, ...other) {
      console.log(x, y, other)
    }
    fn(1, 2)        // 1 2 []
    fn(1, 2, 3, 4)  // 1 2 [3, 4]

    // 3. 展开运算符:把数组拆散成一个个元素
    const arr = [1, 2, 3]
    console.log(...arr)            // 1 2 3
    console.log(arr)               // [1, 2, 3] 原数组不受影响
    console.log(Math.max(...arr))  // 3
    console.log(Math.min(...arr))  // 1

    const arr1 = [1, 2, 3]
    const arr2 = [4, 5, 6]
    console.log([...arr1, ...arr2])  // [1, 2, 3, 4, 5, 6]
  </script>
</body>
</html>

关键行与易错点

  • arguments 是伪数组,只能用传统 for 循环遍历,不能调用 forEach;并且只存在于函数内部,在函数外访问会报 arguments is not defined。
  • 剩余参数 ...other 写在形参最末位置;定义时加 ...,使用时不要再加 ...,直接用变量名。
  • 剩余参数得到的是真数组,可以使用 forEach、push 等方法;开发中提倡优先使用剩余参数,arguments 作为了解即可。
  • 剩余参数(凝聚)与展开运算符(拆散)写法相同但语义相反:前者把多个实参收集成数组,后者把数组展开成多个元素。
  • Math.max(arr) 直接传数组会返回 NaN,必须先写 Math.max(...arr) 展开;展开运算符不会修改原数组。

示例二:箭头函数的语法细节与 this 指向 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>箭头函数与 this</title>
</head>
<body>
  <button class="btn1">点击</button>
  <button class="btn2">5秒后启用</button>

  <script>
    // 1. 基本语法:把 function 换成箭头
    const fn = () => {
      console.log('我是箭头函数')
    }
    fn()

    // 2.1 只有一个形参可以省略小括号
    const sum1 = x => x + x
    console.log(sum1(5))   // 10

    // 2.2 函数体只有一句代码,可省略大括号与 return,这句代码就是返回值
    const sum2 = x => x + x
    console.log(sum2(5))   // 10

    // 2.3 返回对象时,要用小括号把对象包裹起来
    const getObj = () => ({ name: '佩奇' })
    console.log(getObj())  // {name: '佩奇'}

    // 2.4 箭头函数没有 arguments,但有剩余参数
    const fn2 = (...other) => {
      console.log(other)   // [1, 2]
    }
    fn2(1, 2)

    // 3. 箭头函数本身没有 this,沿用上一层作用域的 this
    const obj = {
      name: 'andy',
      sayHi: function () {
        const fun = () => {
          console.log(this)  // obj
        }
        fun()
      }
    }
    obj.sayHi()

    // 4. 场景一:点击变红 —— 用普通函数,this 指向按钮
    // document.querySelector('.btn1').addEventListener('click', function () {
    //   this.style.color = 'red'
    // })

    // 改成箭头函数后 this 指向 window,只能重新获取元素
    document.querySelector('.btn1').addEventListener('click', () => {
      document.querySelector('.btn1').style.color = 'red'
    })

    // 5. 场景二:点击禁用,5 秒后启用 —— 定时器里要用 this 就用箭头函数
    document.querySelector('.btn2').addEventListener('click', function () {
      this.disabled = true
      setTimeout(() => {
        console.log(this)      // btn2,沿用外层普通函数的 this
        this.disabled = false
      }, 5000)
    })
  </script>
</body>
</html>

关键行与易错点

  • 箭头函数属于表达式函数,不存在函数提升,必须先声明再调用。
  • 只有一个形参时才能省略小括号;没有参数必须写空小括号 ();有两个及以上参数不能省略;有剩余参数 (...other) 时也不能省略小括号,否则报“意外的标记”。
  • 省略大括号时才会自动 return;一旦写了大括号,就必须手动写 return。
  • 返回对象字面量必须写成 () => ({ name: '佩奇' }),直接写 {} 会被当成函数体,返回 undefined。
  • 箭头函数内没有 arguments,会报 arguments is not defined,需要动态获取实参时用剩余参数。
  • 事件回调里想用 this 拿到 DOM 元素,就用普通函数;定时器等内部函数想沿用外层 this,就用箭头函数。

示例三:解构赋值与 ES6 对象简写 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>解构赋值与对象简写</title>
</head>
<body>
  用户名: <input type="text" class="username"><br>
  密 码: <input type="password" class="password"><br>
  <button>点击</button>

  <script>
    // 1. 数组解构:按位置一一对应
    const [a, b, c] = [1, 2, 3]
    console.log(a, b, c)  // 1 2 3

    // 交换两个变量的值
    let x = 1
    let y = 2
    ;[y, x] = [x, y]
    console.log(x, y)     // 2 1

    // 2. 变量和值不匹配的情况
    const [m, n, o, p = '三星'] = ['小米', '华为', '苹果']
    console.log(m, n, o, p)   // 小米 华为 苹果 三星(默认值兜底)

    const [e, ...others] = ['小米', '华为', '苹果']
    console.log(e, others)    // 小米 ['华为', '苹果']

    const [q, , s] = ['小米', '华为', '苹果']  // 逗号占位,忽略某些值
    console.log(q, s)         // 小米 苹果

    // 3. 对象解构:变量名要和属性名一致
    const user = {
      username: '小明',
      age: 18
    }
    const { username, age } = user
    console.log(username, age)   // 小明 18

    // 重命名:属性名: 新变量名
    const { username: uname } = user
    console.log(uname)           // 小明

    // 4. 解构后台返回的数据,并在函数形参中直接解构
    const msg = {
      code: 200,
      msg: '获取新闻列表成功',
      data: [
        { id: 1, title: '5G商用自己,三大运用商收入下降', count: 58 },
        { id: 2, title: '国际媒体头条速览', count: 56 }
      ]
    }
    function render({ data: myData }) {
      console.log(myData)
    }
    render(msg)

    // 5. ES6 对象属性与方法的简写
    document.querySelector('button').addEventListener('click', function () {
      const username = document.querySelector('.username').value
      const password = document.querySelector('.password').value
      const obj = {
        username,     // 属性名和属性值同名,可只写属性名
        password,
        sayHi() {     // 方法简写,不再写 function
          console.log('hi~')
        }
      }
      console.log(obj)
    })
  </script>
</body>
</html>

关键行与易错点

  • 数组解构按位置赋值:变量多、值少时多出来的变量是 undefined,可写 d = '三星' 设默认值;变量少、值多时可用 ...others 收集剩余项。
  • 用逗号占位 const [q, , s] 可以跳过不需要的值,实现按需导入。
  • 以 ( 或 [ 开头的语句前面必须加分号,否则会和上一行连起来解析报错,例如 ;[y, x] = [x, y]。
  • 对象解构要求变量名与属性名一致,不一致会得到 undefined;改名用 属性名: 新变量名,且新变量名不要与外部变量重名,否则报错。
  • 函数形参直接解构 function render({ data }) {},调用时传整个对象即可,只取需要的属性;改名写 { data: myData }。
  • 对象简写要求变量名与属性名相同才能只写一个;方法简写 sayHi() {} 里不能再写 function。

常见错误 ​

① 函数表达式在赋值语句之前调用

  • 现象:TypeError: fun is not a function,控制台停在调用那一行。
  • 原因:var fun 只做了变量提升(值为 undefined),函数表达式整体不参与提升,赋值动作发生在调用之后。
  • 修法:把赋值挪到调用之前;若确实需要"先调用后定义",改用函数声明 function fn(){}。
js
fun()                 // ❌ TypeError
var fun = function () {}
fun()                 // ✅

fn()                  // ✅ 函数声明可以先用后写
function fn() {}

② 剩余参数没放在形参最后,或在函数体内又写成 ...rest

  • 现象:SyntaxError: Rest parameter must be last formal parameter;或函数体里 console.log(...other) 拿到的不是数组。
  • 原因:... 只能出现在形参列表末尾;函数体内 ... 是展开运算符,语义完全不同。
  • 修法:形参写作 (x, y, ...other);函数体内直接用 other,不要再带三个点。
js
function sum(x, ...rest, y) {}   // ❌ 语法错误
function sum(x, ...rest) {}      // ✅

function f(...rest) { console.log(...rest) }  // 打印 1 2,不是 [1,2]
function f(...rest) { console.log(rest) }     // ✅ 打印 [1,2]

③ 箭头函数返回对象时漏掉外层小括号

  • 现象:const fn = () => { name: '佩奇' } 调用后得到 undefined,控制台里 name 也不高亮。
  • 原因:{} 被解析成函数体而不是对象字面量,里面的 name: 被当成标签语句。
  • 修法:用小括号把对象包起来 () => ({ name: '佩奇' }),或老老实实写 return {}。

④ 在箭头函数里用 this 操作 DOM / 拿事件源

  • 现象:Cannot read properties of undefined (reading 'style'),或点了按钮颜色不变、this 打印出来是 window。
  • 原因:箭头函数没有自己的 this,沿用外层作用域;事件回调外层是全局作用域,this 就是 window。
  • 修法:需要 this 指向事件源的场景,回调写成普通函数 function () {};确实要用箭头函数,就用 document.querySelector(...) 显式取元素。
js
btn.addEventListener('click', function () { this.style.color = 'red' })  // ✅
btn.addEventListener('click', () => { this.style.color = 'red' })        // ❌ this 是 window

⑤ 定时器里用普通函数,想沿用外层 this

  • 现象:5 秒后按钮没有恢复,this 打印为 window,this.disabled = false 静默失效或报错。
  • 原因:setTimeout 由 window 调用,普通回调里的 this 指向 window。
  • 修法:定时器回调换成箭头函数,让它沿用外层(事件处理函数)的 this。
js
btn.addEventListener('click', function () {
  this.disabled = true
  setTimeout(() => { this.disabled = false }, 5000)   // ✅ 箭头沿用 btn
})

⑥ 在箭头函数里使用 arguments,或用 forEach 遍历 arguments

  • 现象:ReferenceError: arguments is not defined;或 arguments.forEach is not a function。
  • 原因:箭头函数没有 arguments 绑定;arguments 是伪数组,没有数组方法。
  • 修法:箭头函数用 (...rest) 代替;必须用 arguments 时用传统 for 循环或 Array.from(arguments)。

⑦ 对象解构的变量名写错,或与外部同名变量冲突

  • 现象:解构结果全是 undefined;或 SyntaxError: Identifier 'username' has already been declared。
  • 原因:对象解构按属性名匹配,不是按位置;const { username } = user 会新建同名变量,和外部 const username 重复声明。
  • 修法:属性名与变量名不一致或重名时用重命名语法 { username: uname };只取值不建变量的场景才可考虑重命名规避。
js
const username = 'andy'
const { username } = user        // ❌ 重复声明
const { username: uname } = user // ✅
const { nick } = user            // ❌ 对象没有 nick 属性,得到 undefined

⑧ 直接 Math.max(arr) 求数组最值

  • 现象:返回 NaN。
  • 原因:Math.max 接收的是若干个数字参数,不是数组。
  • 修法:展开后传入 Math.max(...arr);Math.max 本身不修改原数组。

⑨ 解构语句紧跟上一行代码时缺少分号

  • 现象:上一行以函数调用/赋值结尾,下一行以 ( 或 [ 开头,报 TypeError: ... is not a function 或意外解析。
  • 原因:JS 自动分号插入(ASI)不覆盖以 (、[ 开头的新行,被当成上一行表达式的延续。
  • 修法:在 (、[ 开头的行首补分号,或上一行末尾显式写分号。
js
const a = 1
;[a, b] = [b, a]        // 行首加分号

;(function () {})()     // IIFE 前加分号

练习 ​

练习 1|参数收集

写一个 sum 函数,能对任意个实参求和;再写一个 sumFrom(start, ...nums),第一个参数作为初始值,其余全部累加。

js
function sum(...nums) {
  let total = 0
  nums.forEach(n => total += n)
  return total
}
function sumFrom(start, ...nums) {
  return nums.reduce((acc, n) => acc + n, start)
}
sum(1, 2, 3)        // 6
sumFrom(10, 1, 2)   // 13

练习 2|展开运算符三连

给定 const arr = [3, 1, 8, 2]、const a = [1, 2]、const b = [3, 4],分别求最大值、最小值、合并成一个新数组,并说明原数组是否被修改。

js
Math.max(...arr)     // 8
Math.min(...arr)     // 1
const merged = [...a, ...b]   // [1,2,3,4]
// 以上均不修改原数组

练习 3|接口数据解构

从下面数据中,只取出 data 并重命名为 list,再用数组解构取第一条的 title 和 count,count 缺失时默认为 0。

js
const msg = { code: 200, data: [{ id: 1, title: '5G商用', count: 58 }] }

const { data: list } = msg
const [{ title, count = 0 }] = list

练习 4|this 判断

下面两段代码能否达到预期?不能的话怎么改?

js
// A:点击后按钮变红
document.querySelector('.btn1').addEventListener('click', () => {
  this.style.color = 'red'
})

// B:点击后禁用,5 秒后恢复
document.querySelector('.btn2').addEventListener('click', function () {
  this.disabled = true
  setTimeout(function () { this.disabled = false }, 5000)
})

参考:A 不行,箭头函数 this 是 window,改为普通函数或显式查询元素;B 不行,定时器回调改为箭头函数 setTimeout(() => { this.disabled = false }, 5000)。

练习 5|综合改写

把下面的传统写法改成"对象简写 + 箭头函数 + 解构参数"的形式。

js
// 原版
function render(options) {
  const data = options.data
  const title = options.title
  return { data: data, title: title, log: function () { console.log(title) } }
}

// 改写
const render = ({ data, title }) => ({
  data,
  title,
  log() { console.log(title) }
})

原视频对照 ​

#视频时长
1函数参数-arguments对象07:54
2剩余参数07:32
3展开运算符08:25
4箭头函数的基本使用12:32
5箭头函数中的this11:54

小结 ​

  1. 判断"能不能先用后写"只看一点:这个东西是声明还是赋值——函数声明能提升,函数表达式、箭头函数、const/let 变量都不能。
  2. 形参个数不确定时优先用剩余参数:它是真数组,能直接上 forEach/reduce,并且是唯一在箭头函数里可用的"收集实参"手段。
  3. arguments 只剩"读老代码"的价值:伪数组、只能在普通函数中用、不能用数组方法,新代码不要主动选它。
  4. 记住一句话区分两个 ...:出现在形参位置是凝聚(收成数组),出现在调用/字面量位置是拆散(展开成元素),方向相反、场景互不重叠。
  5. 解构的匹配规则是"数组看位置、对象看名字":数组缺值给 undefined、可用默认值或 ... 兜底,对象名不一致必须重命名。
  6. 箭头函数的四个省略规则(省括号、省大括号、省 return、返回对象加圆括号)只是语法糖,写不写都不影响功能,可读性优先。
  7. 用不用箭头函数的唯一决策依据是 this 该指向谁:需要指向事件源/调用者就用普通函数,需要沿用外层 this(如定时器、map 回调)就用箭头函数。
  8. 越是"简写"越容易踩坑:返回对象、this、arguments、行首分号这四类问题,几乎都出现在把代码压缩成一行的时候,提交前重点回看这几处。