ES6中解构赋值主要分为6类,分别为 数组解构赋值对象解构赋值字符串解构赋值数值和布尔值解构赋值函数参数解构赋值

什么是解构?

在ES6中允许按照一定的模式从数组和对象中提取值,然后对变量进行赋值,这被称为解构(Destructuring)

数组解构赋值

数组的解构赋值时,等号的右边必须是数组,否则将会报错。只要数据结构具有Iterator借口,则都可以采用数组形式的解构赋值。

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

这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些常见的解构例子:

let [f, [[k], y]] = [1, [[2], 3]];
f // 1
k // 2
y // 3

let [ , ,t] = ["php", "java", "go"];
t // go

let [x, ,y] = ["php", "java", "go"];
x // php
y // go

let [k, ...t] = [1, 2, 3, 4];
k // 1
t // [2, 3, 4]

let [x, y, ...z] = ["php"];
x // php
y // undefined
z // []

当解构不成功,变量的值就等于undefined。下面代码中,f均解构不成功,为undefined。

let [f] = [];
let [k, f] = [1];

Set结构也可以使用数组的解构赋值,如下:

let [x, y, z] = new Set(["php", "java", "go"]);
x // php
y // java
z // go

还有一种情况是部分解构成功,如下:

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

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

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

let [f] = 1;
let [f] = false;
let [f] = NaN;
let [f] = undefined;
let [f] = null;
let [f] = {};

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

数组解构赋值中允许指定 默认值 ,如:

let [f = true] = [];
f // true
let [a, b = 'x'] = ['y'];
a // y
b // x
let [a, b = 'c'] = ['k', undefined];
a // k
b // c

let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null

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还没有被声明。

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

对象解构赋值

对象的解构与数组有一个重要的不同:数组的元素是按次序排列的,变量的取值是由它的位置决定的;而对象的属性没有次序,变量必须与属性同名才能取到正确的值。示例如下:

let {f, b} = {f: "AA", b: "KK"};  // f --> AA    b --> KK
let {c} = {f: "AA", b: "KK"}; // c --> undefined

如果变量名与属性名不一致,必须用如下的形式:

let {f: b} = {f: 'AA', b: 'CC'}; // b --> AA

let obj = {hello: 'AA', world: 'CC'};
let {hello: h, world: w} = obj; // h --> hello    w --> world    hello  --> error: hello is not defined

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

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} = {y : 5}; // y --> 5
let {x = 3} = {x : undefined}; // x --> 3
let {x = 3} = {x : null}; // x --> null

默认值生效的条件是对象的属性值严格等于undefined
如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错,如下:

let {f: {bo}} = {bc: "bucc"}

以上代码报错的原因是因为f此时等于undefined,再取子属性就会报错。

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

// 错误的写法
let x;
{x} = {x : 1}; // SyntaxError: syntax error

// 正确的写法
let x;
({x} = {x : 1});

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

解构赋值允许等号左边的模式之中不放置任何变量名。虽然此类表达式毫无意义,但是语法合法,且可以正常执行:

({} = [true, false]);
({} = 'abc');
({} = []);

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

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

字符串解构赋值

字符串在进行解构赋值时,字符串被转换成了一个类似数组的对象:

let [a, b, c, d, e] = 'hello'; // a -->h   a -->e   a -->l   a -->l   a -->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

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。

let {prop: x} = undefined; // TypeError
let {prop: y} = null;  // TypeError

函数参数解构赋值

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

function add([x, y]) {
  return x + y;
}
add([3, 2]); // 5

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

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

// 示例一
function move ({x = 0, y = 0} = {}) {
  return [x, y]
}
move({x: 3, y: 9});   // [3, 9]
move({x: 3});   // [3, 0]
move({});   // [0, 0]
move();   // [0, 0]

// 示例二
function move({x, y} = {x : 0, y : 0}) {
  return [x, y];
}
move({x: 3, y: 9});   // [3, 9]
move({x: 3});   // [3, undefined]
move({});   // [undefined, undefined]
move();   // [0, 0]

关于圆括号的问题

解构赋值虽然很方便,但是解析起来并不容易。对于编译器来说,一个式子到底是模式还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。
ES6规则是只要有可能导致解构的歧义,就不得使用圆括号。但是这个在实际中并不那么容易判断,且处理起来相当的麻烦。所以也建议,只要有可能就不要在模式中放置圆括号。

不能使用圆括号的情况

情况一:变量声明语句
let [(a)] = [1];  // 错误

let {x : (c)} = {};// 错误
let ({x : c}) = [1];// 错误
let {(x : c)} = [1];// 错误
let {(x) : c} = [1];// 错误

let {o : ({p:p})} = {o: {p: 2}};// 错误

类似以上代码语句都会报错,因为它们都是变量声明语句,模式不能使用圆括号。

情况二:函数参数

函数参数也属于变量声明,因此不能使用圆括号

function f ([(z)]) { return z; }// 错误
function f ([z, (x)]) {return x; }// 错误
情况三:赋值语句的模式
// 将整个模式放入圆括号之中导致报错
({p : a}) = {p: 4};// 错误
([a]) = [5];// 错误

// 将一部分模式放入圆括号之中导致报错
[({p: a}), {x: c}] = [{}, {}];// 错误

可以使用圆括号的情况

可以使用圆括号的情况只有一种:赋值语句的非模式部分可以使用圆括号。

[(b)] = [3];  // 正确
({p: (d)} = {});// 正确
[(parseInt.prop)] = [3];// 正确

以上三个语句均执行正确,因为他们的都是赋值语句,而不是声明的语句,另外它们的圆括号都不属于模式的一部分。

用途

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

交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
从函数返回多个值

函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里面返回。

// 返回一个数组
function test1() {
  return [1, 2, 3];
}
let [a, b, c] = test1();
// 返回一个对象
function test2() {
  return {
    a: 2, 
    b: 6
  };
}
let {a, b} = test2();
函数参数的定义

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

// 参数是一组有序的值
function f1([x, y, z]) { ... }
f1([1, 2, 3]);

// 参数是一组无序的值
function f2({x, y, z}}) { ... }
f2({z: 8, x: 6, y: 4});
提取JSON数据
let jsonData = {
  id: 2,
  name: 'LiMing',
  data: [867, 22]
}
let {id, name, data: nums} = jsonData;
console.log(id, name, nums);
// 2, "LiMing", [867, 22]
函数参数的默认值
jQuery.ajax = function (url, {
  async = true,
  cache = true,
  complate = function(){}
}) {
  // do stuff
} 
遍历Map结构

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

let map = new Map();
map.set('one', 'hello');
map.set('two', 'world');

for(let [key , value] of map) {
  console.log(key, value);
}

// 获取键名
for(let [key] of map) {
  console.log(key);
}

// 获取键值
for(let [, value] of map) {
  console.log(value);
}
输入模块的指定方法:

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

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

标签: JavaScript, ES6, 变量的解构赋值, 数组解构赋值, 字符串解构赋值, 数值和布尔值解构赋值, 函数参数解构赋值, 变量的解构赋值用途, 变量解构赋值的相关问题

添加新评论