跳转到内容

第三章 变量的解构赋值

这一章节阮一峰老师主要讲解 解构 的知识点,分别从 数组、对象、字符串、数值和布尔值、函数参数 等方向着手,讲解它们的使用方式。最后还谈了解构的问题与用途。

数组的解构赋值

书中从 基本用法、默认值 两个方面谈数组的解构。

基本用法

ES6 允许从数组和对象中按照一定模式获取值并赋值给变量,这一行为被称为解构赋值,其写法如下:

js
let [a, b, c] = [1, 2, 3]
console.log(a,b,c) // 1,2,3

本质上是遵循 “模式匹配” 原则,只要等式两边模式相等,就可以对应赋值。如:

js
let [a, [[b], c]] = [1, [[2], 3]]
console.log(a,b,c) // 1,2,3

若解构不成功或者对应位置没有值,则变量的值会等于 undefined ;若解构不完全(即等式右边的值比左侧多),则会正常解构赋值。示例代码如下:

js
// 解构不成功
let [a, b] = [1]
console.log(a,b) // 1,undefined

// 解构不完全
let [a, [[b], c]] = [1, [[2,3], 4]]
console.log(a,b,c) // 1,2,4

如果等式右侧是不允许遍历的解构,则会直接报错。

js
// 不可以解构,会报错
let [tmp] = 1
let [tmp] = false
let [tmp] = NaN
let [tmp] = undefined
let [tmp] = null
let [tmp] = {}

// Set 允许解构,本质是可以遍历的
let [tmp, foo] = new Set(['1', '2'])
console.log(tmp, foo) // '1', '2'

默认值

解构赋值允许设置默认值。ES6 内部严格使用相等运算符判断该位置是否有值,只有 === undefined 才会把默认值赋值给变量。示例代码如下:

js
let [a, b, c] = [1, , null]
console.log(a,b,c) // 1,undefined,null

若使用表达式作为默认值,则该表达式是惰性的,在需要使用时才会执行。

js
function f() {
  return 1
}

let [x = f()] = [2]

// 上方代码可以等价替换为如下形式

let x
if ([2][0] === undefined) {
  x = f()
} else {
  x = [2][0]
}

默认值也可以设置为其他变量,前提是该变量必须已经声明,否则会报错。

js
let [x = 1, y = x] = [2] // x=2,y=2
let [x = 1, y = x] = [1, 2] // x=1,y=2
let [x = y, y = 2] = [2] // 2,2 这里没用到y,所以没报错
let [x = y, y = 2] = [] // 报错

对象的解构赋值

与数组一样,对象也可以使用解构。

数组解构需要讲究顺序一一对应,对象解构没有顺序要求,有名称要求,需要名称一一对应。

同样的,如果解构没有对应的值,则会赋值 undefined

js
let {a, b, c} = {a: 1, b: 3}
console.log(a,b,c) // 1,3,undefined

对象解构同样允许多层数组对象嵌套解构,示例代码如下:

js
let {a, b, b: [, {c}]} = {a: 1, b: [2, {c: 3}]}
console.log(a,b,c) // 1, [2,{c:3}], 3

上方代码中, b: [...] 的写法表示的是在 b 是匹配模式,冒号后面才是真正要赋值的变量。

对象解构也可以赋值默认值,与数组解构相同的,可以赋值变量,但是必须是已声明的变量。

js
let {a = 1, b = a} = {b: 3}
console.log(a,b) // 1,3

解构赋值可以给一个已存在的变量赋值。

js
let obj = {}

let {foo: obj.props} = {foo: {num:1, type: 2}}
console.log(obj) // {props: {num: 1, type: 2}}

如果要将已经声明的变量用于解构赋值,需要小心避免将大括号写在行首,以避免被 JavaScript 引擎误解为代码块而导致语法错误。正确的做法是将整个解构赋值语句放在圆括号里面。

js
let str
{str} = {str: 'daodao'} //  SyntaxError: syntax error 

// 正确写法
let str
({str} = {str: 'daodao'})

由于数组本质是特殊的对象 因此可以对数组进行对象属性的解构。

js
 let arr = [l, 2 , 3]
 let {0: first, [arr.length - l]: last} = arr;  first // 1
 last // 3

上面的代码对数组进行对象解构 数组 arr 键对应的值是 1, [arr.length - 1] 对应的值是 3。

字符串的解构赋值

字符串可以转换成类似数组的对象,因此可以对其解构。

js
const [a,b,c,d,e] = 'daodao'
console.log(a,b,c,d,e) // d,a,o,d,a

同样的,类似数组的对象也有 length 属性,因此也可以对该属性解构。

js
const {length: len} = 'daodao'
console.log(len) // 6

数值和布尔值的解构赋值

解构赋值时,如果等号右侧是数值型或布尔值型,会先转为对象。

js
let {toString: a} = 123
a === Number.prototype.toString // true

let {toString: a} = false
a === Boolean.prototype.toString // true

🔔 提示

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于 undefinednull 无法转为对象,所以对它们进行解构赋值时都会报错。

函数参数的解构赋值

对于函数的参数,可以使用解构赋值的方式来提取数组或对象中的值。通过解构赋值,可以更方便地获取传入参数的值并在函数体内使用。

js
[[1,2], [3,4]].map(([x, y]) => x + y) // [3, 7]

函数参数解构赋值中可以设置默认值。通过在解构赋值语法中设置默认值,可以确保在未传入参数或传入 undefined 时,变量能够取到默认值。

js
function fn({x = 0, y = 0} = {}) {
  return [x, y]
}

fn({x: 3, y:8}) // [3, 8]
fn({x: 3}) // [3, 0]
fn({}) // [0, 0]
fn() // [0, 0]

若修改写法,则是给整个函数设置默认值,结果也会不一样。

js
function fn({x, y} = {x = 0, y = 0}) {
  return [x, y]
}

fn({x: 3, y:8}) // [3, 8]
fn({x: 3}) // [3, undefined]
fn({}) // [undefined, undefined]
fn() // [0, 0]

当传入参数中出现 undefined 时触发函数参数默认值。

js
[1, , 3].map((x = 'undefined') => x) // [1, 'undefined', 3]

圆括号问题

圆括号有可能会导致解构赋值产生歧义,因此 ES6 作了规定:赋值语句允许添加括号,声明语句不允许添加括号。

js
// 赋值语句
[(b)] = {3}
({p: (d)} = {})
[(obj.prop)] = [3]

// 声明语句
let [(a)]= [1]; 
let {x: (c)} = {} ; 
let ({x: c}) = {}; 
let {(x: c)} = {}; 
let {(x): c} = {}; 
let {o: ({p: p })} = { o: { p: 2 J }};

用途

解构赋值在现实生产环境中有很多实用的用途,阮一峰老师在书中罗列了几点:

  1. 交换值:在之前交换值需要使用到中间变量,其实通过解构赋值也能做到。

  2. 函数返回多个值:函数 return 只能返回一个值,若有多个值需要通过数组或对象的形式返回。解构可以获取到需要的值。

  3. 函数参数定义:解构赋值可以方便地将一组参数与变量名对应起来。

  4. 提取 JSON 数据:可以通过解构快速提取 JSON 格式的数据。

  5. 函数参数默认值:函数参数可以通过解构设置默认值,不再需要额外赋值操作。

  6. 遍历 Map 结构:Map 可以使用 for...of... 循环遍历,配合解构获取键名和键值就很方便。

  7. 模块指定方法:加载模块时,往往需要指定输入的方法。解构赋值使得输入语句非常清晰

js
let x = 1
let y = 2
[x, y] = [y, x]
js
function fn1() {
  return [1,2,3,4]
}
const [a, , c] = fn1()

function fn2() {
  return {
    bar: 1,
    foo: 2
  }
}
const {bar} = fn2()
js
// 有序
function fn([x, y, z]) {
  // ...
}
fn([1, 2, 3])

// 无序
function fn({x, y, z}) {
  // ...
}
fn({x: 4, y: 5, z: 6})
js
let data = {
  id: 1,
  name: 'dd',
  num: [113, 24]
}

let {id, name, num} = data
js
function fn({
  url: 'xxxxxxx',
  method: 'post'
}) {}
js
let map = new Map([
  ['first', '1'],
  ['second', '2'],
])

for(let [key, value] of map) {
  console.log('key:' + key, 'value:' + value)
}
js
const { SourceMapConsumer, SourceNode } = require('source-map');

总结

阮一峰老师在这一章节主要讲解了解构赋值的知识点,包括对数组、对象、字符串、数值和布尔值、函数参数等进行解构赋值的使用方式,并说明了解构赋值的问题和用途。

  • 在数组的解构赋值中,可以利用模式匹配原则对数组进行赋值,同时也可以设置默认值,并且可以给已存在的变量赋值。
  • 对象的解构赋值不需要考虑顺序,只需保证名称对应即可,同样可以设置默认值,并可以将已存在的变量用于解构赋值。
  • 字符串也可以通过解构赋值的方式进行赋值。
  • 数值和布尔值在解构赋值时会先转为对象。
  • 函数参数的解构赋值可以方便地获取传入参数的值,并可以设置默认值。
  • 圆括号有可能会导致解构赋值产生歧义,在赋值语句中可以添加括号,但在声明语句中不允许添加括号。

此外,解构赋值在实际生产环境中有很多实用的用途,包括交换值、函数返回多个值、函数参数定义、提取 JSON 数据、函数参数默认值、遍历 Map 结构、以及模块指定方法等。