解构赋值

解构赋值是一种 JavaScript 表达式,通过解构赋值可以很方便地将属性从对象或数组中取出并赋值给变量。

比如以下就是一个数组的解构赋值:

$$jsdemo$$
$$edit$$
let [a, b] = [2, 6]
alert(a) // 2
alert(b) // 6

数组解构

数组的解构将被赋值的变量放置在方括号中,赋值符的右边放置数组,数组中的值便会按顺序赋值给左边方括号中的变量。

$$jsdemo$$
$$edit$$
let arr = [1, 2, 3]
let [x, y, z] = arr

alert(x) // 1
alert(y) // 2
alert(z) // 3

如果左边待赋值的变量数量超过数组长度时,未能赋值的变量值为 undefined

$$jsdemo$$
$$edit$$
let arr = [1, 2]
let [x, y, z] = arr

alert(x) // 1
alert(y) // 2
alert(z) // undefined

可以为待解构的变量准备一个默认值,如果未取得值则采用默认值。

$$jsdemo$$
$$edit$$
let arr = [1, 2]
let [x = 100, y = 200, z = 300] = arr

alert(x) // 1
alert(y) // 2
alert(z) // 300

交换变量值

通常我们交换变量值时需要用一个中间变量,写成以下代码:

$$jsdemo$$
$$edit$$
let a = 1
let b = 2
// 交换 a、b
let t = a
a = b
b = t

alert(a) // 2
alert(b) // 1

如果使用解构赋值的话将会非常高效。

$$jsdemo$$
$$edit$$
let a = 1
let b = 2
// 交换 a、b
;[a, b] = [b, a]

alert(a) // 2
alert(b) // 1

$$tip

默认情况下我们使用换行符来区分每行代码足以。

但如果方括号在开头,由于方括号属于操作符,操作符是可以换行的,解释器就会将方括号与上一行代码归成一行代码。在这种情况下应该在方括号与上一行语句间插入 ;(分号)明确语句的分隔。

$$

$$tip

解构赋值不止可以交换两个变量哦。

$$

解构对象

同样可以用解构赋值的方式获取对象中属性,属性会按照属性名赋值到变量上。

$$jsdemo$$
$$edit$$
let { a, b } = { b: 2, a: 1 }
alert(a) // 1
alert(b) // 2

无声明赋值

以上的代码都是在声明变量的同时进行对象解构赋值,如果事先声明了变量而后再解构赋值需要加个括号,不然会报语法的错误。因为 JavaScript 将 { a, b} 当作了大括号包裹的代码块。

$$jsdemo$$
$$edit$$
let a, b
;({ a, b } = { b: 2, a: 1 }) // 别忘了加括号
alert(a) // 1
alert(b) // 2

默认值

同样的,对象的解构中如果未获取到值那么变量会是 undefined ,可以提前配置一个默认值。

$$jsdemo$$
$$edit$$
let { apple, banana = "香蕉" } = { apple: "苹果" }
alert(apple) // 苹果
alert(banana) // 香蕉

新的变量名赋值

解构出来的属性可以赋值给不同的变量名。

$$jsdemo$$
$$edit$$
let { apple, banana: xiangjiao } = { apple: "苹果", banana: "香蕉" }
alert(apple) // 苹果
alert(xiangjiao) // 香蕉

实战:函数命名参数

JavaScript 没有命名参数的语法,但可以通过解构赋值变相地实现。

当我们调用一个参数很多的函数时,记住每一个参数的位置是很难的。

$$jsdemo$$
$$edit$$
function alertStudent(name, gender, age, school) {
    alert(`名字是${name},性别是${gender},年龄是${age},学校是${school}。`)
}

alertStudent("鸣人", "男性", "18", "忍者学校")

于是乎人们想到了通过对象传递参数的方式,但是这样子会造成疑惑。

$$jsdemo$$
$$edit$$
// 程序员会通过函数定义的参数判断需要传递的数据
// 这里的 student 对象会造成疑惑
// 不知道 student 对象中有什么键值
function alertStudent(student) {
    alert(
        `名字是${student.name},性别是${student.gender},年龄是${student.age},学校是${student.school}。`
    )
}

alertStudent({
    name: "鸣人",
    gender: "男性",
    age: "18",
    school: "忍者学校",
})

使用解构赋值就可以帮我们直接在调用函数的同时解构到各个参数上。

$$jsdemo$$
$$edit$$
function alertStudent({ name, gender, age, school }) {
    // 这里等同于
    // let { name, gender, age, school } = { name: "鸣人", gender: "男性", age: "18",school: "忍者学校" }
    alert(`名字是${name},性别是${gender},年龄是${age},学校是${school}。`)
}

alertStudent({
    name: "鸣人",
    gender: "男性",
    age: "18",
    school: "忍者学校",
})

当然使用解构赋值的同时,也是可以重命名变量或使用默认值。

$$jsdemo$$
$$edit$$
function alertStudent({
    name: xingming = "无名氏",
    gender,
    age,
    school: xuexiao,
}) {
    // 这里等同于
    // let { name: xingming = "无名氏", gender, age, school } = { name: "鸣人", gender: "男性", age: "18",school: "忍者学校" }
    alert(
        `名字是${xingming},性别是${gender},年龄是${age},学校是${xuexiao}。`
    )
}

alertStudent({
    gender: "男性",
    age: "18",
    school: "忍者学校",
}) // 名字是无名氏,性别是男性,年龄是18,学校是忍者学校。

$$warning

使用默认值时要注意,如果连空对象都没传,那么会报错,比如以下的示例。

$$

$$jsdemo$$
$$edit$$
function alertStudent({ name = "无名氏", gender = "男" }) {
    alert(`名字是${name},性别是${gender}。`)
}

// 正常
// 等同于
// let { name = "无名氏", gender = "男" } = {}
alertStudent({})

// Cannot read properties of undefined (reading 'name')
// 等同于
// let { name = "无名氏", gender = "男" } = undefined
alertStudent()

针对这种情况,我们可以给解构参数一个默认值。

$$jsdemo$$
$$edit$$
function alertStudent({ name = "无名氏", gender = "男" } = {}) {
    alert(`名字是${name},性别是${gender}。`)
}

// 正常
// 等同于
// let { name = "无名氏", gender = "男" } = {}
alertStudent({})

// 正常,因为使用 {} 默认值。
alertStudent()

剩余语法 ...

剩余语法则是将多个元素收集起来并“凝聚”为单个元素。

剩余语法 ... 可以以数组的形式接收解构赋值中余下的变量。

$$jsdemo$$
$$edit$$
let arr = [1, 2, 3, 4, 5, 6]
let [a, b, ...c] = arr

alert(a) // 1
alert(b) // 2
alert(c) // 3,4,5,6 ,数组

在对象解构中也可使用剩余语法接收余下的值。

$$jsdemo$$
$$edit$$
let { apple, ...rest } = { apple: "苹果", banana: "香蕉", cherry: "樱桃" }
console.log(apple) // 苹果
console.log(rest) // { banana: "香蕉", cherry: "樱桃" }

收集参数

剩余参数同样可以用于收集多个参数值,将其打包成一个数组对象.。我们使用过的一些能接收无限多个参数的方法就是这么实现的。

$$jsdemo$$
$$edit$$
function alertNames(schoolMaster, ...names) {
    alert(`我是${schoolMaster}校长。`)
    for (let name of names) {
        alert(`${name}你好。`)
    }
}

// 等同于
// let [schoolMaster, ...names] = ["邓布利多", "鸣人", "路飞", "柯南"]
alertNames("邓布利多", "鸣人", "路飞", "柯南")

展开语法 ...

展开语法与剩余语法看起来是一样的,具体应用哪个语法要看所处的位置。展开语法则是将单个元素拆解为多个元素。

展开语法 ... 可以将数组展开,用这个特性可以实现数组的合并等功能。

$$jsdemo$$
$$edit$$
let arr = [1, 2, 3]
let arr2 = [4, 5, 6]

let merge = [...arr, ...arr2]
alert(merge) // 1,2,3,4,5,6

对于展开对象当然也是游刃有余,也可以轻松合并对象,但是需要注意,由于对象属性名是唯一的,后来展开的对象属性会覆盖之前同名的属性。

$$jsdemo$$
$$edit$$
let obj = { a: 1, b: 2 }
let obj2 = { a: 0, c: 3 }

let merge = { ...obj, ...obj2 }

// 注意, obj 的 a 被 obj2 的 a 覆盖了
console.log(merge) // {a: 0, b: 2, c: 3}

$$tip 一个不严谨的规则,当 ... 处于赋值操作符左侧时属于收集语法,右侧时属于展开语法。 $$

展开参数

可以把展开语法用于函数调用时,此时会把数组内的元素展开作为参数。

$$jsdemo$$
$$edit$$
function alertInfo(name, age, height) {
    alert(`我叫${name},年龄是${age},身高是${height}。`)
}

let info = [20, 1.8] // 年龄,身高

// 等同于
// let [name, age, height] = ["鸣人", ...info]
alertInfo("鸣人", ...info) // 我叫鸣人,年龄是20,身高是1.8。

$$tip 一个不严谨的规则,当 ... 用于函数参数定义时属于收集参数,函数调用时属于展开参数。 $$

实战:装饰器模式,统计函数耗时

装饰器模式允许我们在不影响原函数的前提下扩展一个函数的功能,装饰后的函数调用接口与返回数据都应保持一致。

剩余与展开语法使得我们可以接收任意参数,并展开至原函数,这意味着装饰器可以匹配任意参数的函数。

以下实现了一个统计函数耗时的装饰器。

$$jsdemo$$
$$edit$$
function loop(a, b) {
    // 在 a,b 区间内循环并返回它们的减值

    for (let i = a; i <= b; i++) {
        console.log(i)
    }
    return b - a
}

function UsageTime(func) {
    // 计算函数的耗时
    // 又不影响原功能
    return function wrapper(...args) {
        // 使用剩余语法
        // 接受任意多个参数值
        // 使得可以适合任意函数

        console.log("进入时确认:")

        // 开始的时间戳
        let begin = new Date().getTime()

        // 展开接收到的参数至原函数
        let result = func(...args)

        // 结束的时间戳
        let end = new Date().getTime()

        console.log(`执行耗时${end - begin}毫秒`)

        console.log("结束时确认:")
        return result
    }
}

let newLoop = UsageTime(loop)

let result = newLoop(100, 1000)
console.log(result)

练习

  1. 交换 x y z 的值。
let numbers = {
    x: 1,
    y: 2,
    z: 3,
}

// 补充代码交换 x y z 的值

// 交换后的结果
alert(numbers.x) // 3
alert(numbers.y) // 1
alert(numbers.z) // 2

$$answer

$$jsdemo$$
$$edit$$
let numbers = {
    x: 1,
    y: 2,
    z: 3,
}

;[numbers.x, numbers.y, numbers.z] = [numbers.z, numbers.x, numbers.y]

// 交换后的结果
alert(numbers.x) // 3
alert(numbers.y) // 1
alert(numbers.z) // 2

$$

  1. 用定义好的变量 mingzi shengao zhandou 解构出对象 person 中对应的值。
let person = {
    name: "鸣人",
    height: 1.8,
    fight() {
        alert("螺旋丸")
    },
}

let mingzi
let shengao
let zhandou

// 补充代码解构变量

alert(mingzi) // 鸣人
alert(shengao) // height

zhandou() // 螺旋丸

$$answer

$$jsdemo$$
$$edit$$
let person = {
    name: "鸣人",
    height: 1.8,
    fight() {
        alert("螺旋丸")
    },
}

let mingzi
let shengao
let zhandou

// 获取对象里的值
;({ name: mingzi, height: shengao, fight: zhandou } = person)

alert(mingzi) // 鸣人
alert(shengao) // height

zhandou() // 螺旋丸

$$

  1. 补充函数的参数定义。
function signup() {
    // 补充函数的参数定义
    // 要求:
    // 1. 使用解构赋值,命名参数
    // 2. 未设置密码时默认为:123456

    if (!username) {
        alert("请输入用户名")
        return
    }

    alert(`注册成功,用户名:${username},密码:${password}`)
}

// 注册成功,用户名:三眼鸭,密码:456789
signup({ password: "456789", username: "三眼鸭" })

// 注册成功,用户名:三眼牛,密码:123456
signup({ username: "三眼牛" })

// 请输入用户名
signup({ password: "abcdef" })

// 请输入用户名
signup()

$$answer

$$jsdemo$$
$$edit$$
function signup({ username, password = "123456" } = {}) {
    // 补充函数的参数定义
    // 要求:
    // 1. 使用解构赋值,命名参数
    // 2. 未设置密码时默认为:123456

    if (!username) {
        alert("请输入用户名")
        return
    }

    alert(`注册成功,用户名:${username},密码:${password}`)
}

// 注册成功,用户名:三眼鸭,密码:456789
signup({ password: "456789", username: "三眼鸭" })

// 注册成功,用户名:三眼牛,密码:123456
signup({ username: "三眼牛" })

// 请输入用户名
signup({ password: "abcdef" })

// 请输入用户名
signup()

$$

  1. 定义一个函数,接收任意多个数字作为参数,返回其中的最大值。
function getMax() {
    // 补充完成
}

// 70
alert(getMax(20, 5, 7, -10, 70, 2))

$$answer

$$jsdemo$$
$$edit$$
function getMax(max, ...arr) {
    for (let n of arr) {
        if (n > max) max = n
    }

    return max
}

// 70
alert(getMax(20, 5, 7, -10, 70, 2))

$$

  1. 存在多个数组保存的数字,定义一个函数,并完成调用,求出数组中数字的最小值。
function getMin() {
    // 补充完成
}

// 求出 numbers 的最小值
let nums1 = [10, -20, 3, -10]
let nums2 = [7, 6, 2, -40]
let nums3 = [15, -10, 80]

// 完成调用
// -40

$$answer

$$jsdemo$$
$$edit$$
function getMin(min, ...arr) {
    for (let n of arr) {
        if (n < min) min = n
    }

    return min
}

// 求出 numbers 的最小值
let nums1 = [10, -20, 3, -10]
let nums2 = [7, 6, 2, -40]
let nums3 = [15, -10, 80]

// -40
alert(getMin(...nums1, ...nums2, ...nums3))

$$