二、变量的解构赋值

数组的解构赋值

基本用法

let [a, b, c] = [1, 2, 3]

上面的代码表示,可以从数组中提取值,按照对应位置对变量赋值。

本质上,这种写法属于 "模式匹配" ,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

下面是一些使用嵌套数组进行解构的例子。

let [foo, [ [bar], baz] ] = [1, [ [2], 3 ]] 

foo  //1

bar // 2

baz // 3

 

let [, , third] = ["foo", "bar", "baz"]

third  // "baz"

 

let [x, , y] = [1, 2, 3]

x // 1

y // 3

 

let [head, ....tail] = [1, 2, 3, 4]

head  // 1

tail    //  [2, 3, 4]

 

let [x, y, ...z] = ['a']

x  // "a"

y  // undefined

z // []

如果结构不成功,变量的值就等于 undefined

 

let [foo] = []

let [bar, foo] = [1]

以上两种情况都属于解构不成功, foo的值都会等于 undefined

 

另一种情况是不完全结构,即等号左边的模式只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

let [x, y] = [1, 2, 3]

x  // 1

y  // 2

 

let [a, [b], d] = [1, [2, 3], 4]

a // 1

b // 2

d // 4

上面两个例子都属于不完全解构,但是可以成功。

如果等号的右边不是数组(或者严格来说不是可遍历的结构),那么将会报错。

 

// 报错

let [foo] = 1

let [foo] = false

let [foo] = NaN

let [foo] = undefined

let [foo] = null

let [foo] = {}

上面的语句都会报错,因为等号右边的值或是转为对象以后不具备 Iterator 接口(前五个表达式),或是本身就不具备Iterator接口(最后一个表达式)。

对于Set结构,也可以使用数组的解构赋值。

let [x, y, z] = new Set(['a', 'b', 'c'])

x  // "a"

 

事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。

function* fibs() {

      let a = 0

      let b = 1

      while(true){

          yield a

         [a, b] = [b, a+b]

     }

}

 

let [first, second, third, fourth, fifth, sixth] = fibs()

sixth  // 5

 

解构赋值允许指定默认值。

let [foo = true] = []

foo // true

 

let [x, y="b"] = ["a"]  // x="a", y="b"

let [x, y="b"] = ["a", undefined]  // x="a", y="b"

 

注意:ES6内部使用严格相等运算符(===)判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined,默认值是不会生效的。

 

let [x = 1] = [undefined]

x // 1

 

let [x = 1] = [null]

x // null

上面的代码中,如果一个数组成员是null,默认值就不会生效,因为null不严格等于undefined。

 

如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到时才会求值。

function f() {

    console.log('aaa')

}

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

上面的代码中,因为x能取到值,所以函数f根本不会执行。

 

默认值可以引用解构赋值的其他变量,但该变量必须已经声明。

let [x = 1, y = x] = []       // x=1, y=1

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 = 1] = []         // ReferenceError

上面最后一个表达式之所以会报错,是因为x用到默认值y时,y还没有声明。  

 

对象的解构赋值

let {foo, bar} = { foo: "aaa", bar: "bbb" }

foo  // "aaa"

bar  // "bbb"

 

对象的解构与数组有一个重要的不同。

数组的元素是按次序排列的,变量的取值是由它的位置决定的。

而对象的属性没有次序,变量必须与属性同名才能取到正确的值。

let {bar, foo} = {foo: "aaa", bar: "bbb"}

foo   // "aaa"

bar  //  "bbb"

 

let { baz } = { foo: "aaa", bar: "bbb" } 

baz  // undefined

上面代码的第一个例子中,等号左边的两个变量的次序与等号右边两个同名属性的次序不一致,但是对取值完全没有影响。

第二个例子的变量没有对应的同名属性,导致取不到值,最后等于 undefined。

 

如果变量名与属性名不一致,必须写成下面这样。

let { foo: bar } = { foo: "aaa", bar: "bbb" }

baz  // "aaa"

 

let obj = { first: "hello", last: "world" }

let { first: f, last: l } = obj

f  // "hello"

l  // "world"

实际上说明,对象的解构赋值是下面形式的简写。

let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" }

也就是说,对象的解构赋值的内部机制是先找到同名属性,然后再赋值给对应的变量。真正被赋值的是后者,而不是前者。

let { foo: baz } = { foo: "aaa", bar: "bbb" }

baz   // "aaa"

foo    // error:  foo is not defined

上面的代码中, foo 是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式 foo。

 

与数组一样,解构也可以用于嵌套结构的对象。

let obj = {

    p: [

          'Hello',

          { y: 'World' }

    ]

}

let { p: [x, { y }] } = obj

x  //  "Hello"

y   //  "World"

注意:这时 p 是模式,不是变量,因此不会被赋值。如果 p 也要作为变量赋值,可以写成下面这样。

let { p, p: [ x, { y } ] } = obj

x  // "Hello"

y   // "World"

p  // [ "Hello", { y: "World" } ]

 

下面是另一个例子。

var node = {

     loc:  {

          start: {

              line: 1,

              column: 5

         }

    }

}

 

let  { loc, loc: { start }, loc: { start: { line } }} = node

line  // 1

loc   //  Object { start: Object }

start  //  Object  { line: 1, column: 5 }

上面的代码有三次解构赋值,分别是对loc、 start、 line 三个属性的解构赋值。需要注意的是,最后一次对 line 属性的解构赋值之中,只有 line 是变量, loc 和 start 都是模式, 不是变量。

 

下面是嵌套赋值的例子。

let obj = {}

let arr = [];

({ foo: obj.prop, bar: arr[0] }  = { foo: 123, bar: true })

obj  // {prop: 123}

arr  //  [true]

 

对象的解构也可以指定默认值。

let { x = 3 } = {}

x  // 3

 

let { x, y = 5 } = { x: 1 }

x  // 1

y  // 5

 

let { x: y = 3 } = {}

y // 3

 

let { x: y = 3 } = { x: 5 }

y // 5

 

let { message: msg = 'Something went wrong' } = {}

msg  //  "Something went wrong"

 

默认值生效的条件是,对象的属性值严格等于 undefined

let { x = 3 } = { x: undefined }

x  // 3

 

let { x =  3 } = {x: null}

x  // null

上面的代码中,如果 x 属性等于 null , 就不严格相等于 undefined,导致默认值不会生效。

 

如果解构失败,变量的值等于 undefined。

let { foo } = { bar: 'baz' } 

foo  // undefined

 

如果解构模式时嵌套的对象, 而且子对象所在的父属性不存在,那么将会报错。

let {foo: {bar}} = {baz: 'baz'}

上面的代码中,等号左边对象的foo属性对应一个子对象。该子对象的bar属性在解构时会报错。这是因为 foo 此时等于 undefined,再取子属性就会报错,请看下面的代码。

let _tmp = { baz: 'baz' }

_tmp.foo.bar  // 报错

 

如果要将一个已经声明的变量用于解构赋值,必须非常小心。

// 错误的写法

let x

{x} = {x: 1}  // SyntaxError: syntax error

上面代码的写法会报错,因为JavaScript引擎会将 { x } 理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。

// 正确的写法

let x;

({x} = {x: 1})

上面代码将整个解构赋值语句放在一个圆括号里面,这样就可以正确执行。

 

解构赋值允许等号左边的模式之中不放置任何变量名。因此,可以写出非常古怪的赋值表达式。

({} = [true, false])

  ({} = 'abc')

  ({} = [])

上面的表达式虽然毫无意义,但是语法是合法的,可以执行。

 

对象的解构赋值可以很方便地将现有对象的方法赋值到某个变量。

let { log, sin, cos } = Math

上面的代码将Math对象的对数、正弦、余弦三个方法赋值到对应的变量上,使用起来就会方便很多。

 

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

let arr = [1, 2, 3]

let {0: first, [arr.length - 1]: last} = arr

first  // 1

last  // 3

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

 

字符串的解构赋值

字符串也可以解构赋值。这是因为此时字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = 'hello'

a  //  'h'

b  //  'e'

c  //  'l'

d  //  'l'

e  //  'o'

类似数组的对象都有一个length属性,因此还可以对这个属性进行解构赋值。

let {length: len} = "hello"

len // 5

 

数值和布尔值的解构赋值

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

let {toString: s} = 123

s === Number.prototype.toString    // true

 

let {toString: s} = true

s === Boolean.prototype.toString   // true

上面的代码中,数值和布尔值的包装对象都有 toString属性,因此变量s都能取到值。

 

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

let { prop: x } = undefined   // TypeError

let { prop: y } = null   // TypeError

 

函数参数的解构赋值

函数的参数也可以使用解构赋值。

function add( [ x, y ] ) {

    return x + y

}

add ([1, 2])   // 3

上面的代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y。对于函数内部的代码来说,它们能感受到的参数就是x 和 y。

 

下面是另一个例子。

[ [1, 2], [3, 4] ].map(([a, b]) => a+b)

//  [3, 7]

 

函数参数的解构也可以使用默认值。

function move({x = 0, y = 0} = {}) {

      return [x, y]

}

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

move({x: 3})          //  [3, 0]

move({})               //  [0, 0]

move()                  // [0, 0]

上面的代码中,函数move的参数是一个对象,通过对这个对象进行解构,得到变量x和y的值。如果解构失败, x和y等于默认值。

 

注意:下面的写法会得到不一样的结果。

function move({x, y} = {x: 0, y: 0}) {

      return [x, y]

}

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

move({x: 3})          //  [3, undefined]

move({})               //  [undefined, undefined

move()                  // [0, 0]

上面的代码是为函数move的参数指定默认值,而不是为变量x和y指定默认值,所以会得到与前一种写法不同的结果。

 

undefined 就会触发函数参数的默认值。

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

//  [1, 'yes', 3]

 

用途

变量的解构赋值用途很多。

 

交换变量的值

  let x = 1

  let y = 2

  [x, y] = [y, x]

上面的代码交换变量x和y的值,这样的写法不仅简洁,而且易读,语义非常清晰。



从函数返回多个值 

函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。

// 返回一个数组

function example() {

    return [1, 2, 3]

}

let [a, b, c] = example()

 

// 返回一个对象

function example() {

    return {

        foo: 1,

        bar: 2

   }

}

let {foo, bar} = example()

 

函数参数的定义

解构赋值可以方便地将一组参数与变量名对应起来。

 

// 参数是一组有次序的值

function f([x, y, z]) {...}

f([1, 2 ,3])

 

// 参数是一组无次序的值

function f({ x, y, z})  {...}

f({z: 3, y: 2, x: 1})

 

提取JSON数据

解构赋值对提取JSON对象中的数据尤其有用。

 

let jsonData = {

      id: 42,

      status: "OK",

      data: [867,  5309]

}

 

let { id, status, data: number } = jsonData

 

console.log(id, status, number)   // 42,  "OK",  [867, 5309]

上面的代码可以快速提取JSON数据的值。

 

函数参数的默认值

jQuery.ajax = function (url, {

    async = true,

    beforeSend = function(){},

    cache = true,

    complete = function(){},

    crossDomain = false,

    global = true

}) {

     ......

}

指定参数的默认值,这样就避免了在函数体内部再写 let foo = config.foo || 'default foo' 这样的语句。

 

遍历Map解构

任何部署了Iterator接口的对象都可以用 for...of 循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值获取键名和键值就非常方便。

let map = new Map()

map.set('first', 'hello')

map.set('second', 'world')

 

for(let [key, value] of map) {

    console.log(key + " is " + value)

}

// first is hello

// second is world

 

如果只想获取键名,或者只想获取键值,可以写成下面这样。

 

// 获取键名

for(let [key] of map) {

      ....

}

 

// 获取键值

for(let [,value] of map) {

      ....

}

 

输入模块的指定方法

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

const { SourceMapConsumer, SourceNode } = require("source-map")

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值