时间:2022-11-28 10:28:35 | 栏目:JavaScript代码 | 点击:次
ECMAScript 2015中允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为 解耦赋值 。
在ECMAScript 2015之前中为变量赋值的方式如下所示:
let a = 1; let b = 2; let c = 3;
在ECMAScript 2015允许写成下面这样。
let [a, b, c] = [1, 2, 3];
ECMAScript 2015
的解耦赋值本质上属于模式匹配。赋值运算符两边的模式相同,左边的变量会被赋予对应位置的值。
如果解耦赋值失败的话,变量的值等于 undefined。示例代码如下所示:
// 如果数组的某个索引值的位置的变量,在 = 运算符的右边没有数组中没有对应的索引值与之对应,则解耦赋值失败,其值为 undefined let [v] = [] let [a, b] = [1] console.log(v, a, b); // undefined 1, undefined
如果想解决解耦赋值失败的问题,需要将其赋值运算符的左右两边的数量保持一致。
所谓的不完全解耦赋值就是指赋值运算符的右边数组中的数量要大于左边数组中的数量,导致右边数组中的有些变量失效,但是这这种情况下解耦赋值还是会成功的。
示例代码如下所示:
// 赋值运算符左边变量的数量小于右边值的数量 let [a, b, c] = [1, 2, 3, 4] // 依然会解耦赋值成功 console.log(a, b, c); // 1 2 3
解耦赋值允许指定默认值的。示例代码如下所示:
/* * 解耦赋值中允许指定默认值 * 语法结构如下 * let [var1 = value1, var2 = value2 ,...] = [val1, val2,...] var1,var2 表示变量名 value1,value2 表示默认值 val1,val2, 表示指定的值 */ let [a = 1, b = 2] = [100] console.log(a, b); // 100 2
在使用默认值的时候值得注意的一点就是,ECMAScript6
内部会使用全等于 === 运算符来判断指定位置的值是否全等于undefined
。只有当 全等于undefined时,其默认值才会生效。
测试代码如下所示:
let [a = 1, b = 2] = [100, undefined] console.log(a, b); // 100 2
当我们使用null值时,虽然null
也表示为空,但是null !== undefined
。所以我们的默认值并不会生效,
测试代码如下:
let [a = 1, b = 2] = [100, null] console.log(a, b); // 100 null
由于JavaScript
是一个弱类型的语言,所以赋值号右边是什么类型都是允许的,那就会出现以下几种特殊情况:
情况一:运算符右边是一个函数,示例代码如下
// 1. 运算符右边是一个函数 let [a, b] = [1, function () { return 10; }] console.log(b()); // 10
情况二:运算符右边是一个对象,示例代码如下
// 2. 运算符右边是一个对象 let [a, b] = [1, { name: '狐妖小红娘' }] console.log(b); // { name: '狐妖小红娘' }
情况三:运算符右边函数数组,示例代码如下
// 3. 运算符右边是含有数组 let [a, b] = [1, [2, 3]] console.log(b); // [ 2, 3 ]
情况四:运算符左右两边都含有数组,示例代码如下所示
// 4. 运算符左右两边都含有数组,示例代码如下所示 let [a, [b, c]] = [1, [2, 3]] console.log(b); // 2
对象的解耦赋值是通过变量名称实现与对象的属性名称一一对应实现的。示例代码如下所示:
/* * 对象的解耦赋值 - 从对象中提取值,为变量赋值 ! 变量名称必须与对象的属性名称一一对应,否则就会导致失败。 */ let { x, y } = { x: 10, y: 20 } console.log(x, y); //10 20
值得注意的是赋值运算符两边的格式需要保持一致。
由于 JavaScript
是一个弱类型的语言,所以赋值号右边是什么类型都是允许的,那就会出现以下几种特殊情况:
情况一:运算符右边是一个函数,示例代码如下
// 1. 运算符右边是一个函数 let { a, b } = { a: 1, b: function () { return 10; } } console.log(b());// 10
情况二:运算符右边是一个对象,示例代码如下
// 2. 运算符右边是一个对象 let {a, b} = { a: 1, b: { name: 'ywanzhou' } } console.log(b); // { name: 'ywanzhou' }
情况三:运算符右边函数数组,示例代码如下
// 3. 运算符右边是含有数组 let {a, b} = { a: 1, b: [1, 2] } console.log(b); //[ 1, 2 ]
情况四:运算符左右两边都含有对象,示例代码如下所示
// 4. 运算符左右两边都含有对象 let { m: { name, age }, n } = { m: { name: 'test', age: 20 }, n: 20 } console.log(name, age); // test 20
如果解耦赋值失败的话,变量的值等于undefined。示例代码如下所示:
// 解耦赋值失败 let { a, b } = { a: 10 } console.log(b);
所谓的不完全解耦赋值就是指赋值运算符的右边对象中属性的数量要大于左边对象中属性的数量,导致右边对象中属性的有些变量失效,但是这这种情况下解耦赋值还是会成功的。
// 不完全解耦赋值 let { a } = { a: 10, b: 20 } console.log(a);
解耦赋值允许指定默认值的。示例代码如下所示:
// 默认值 let { a, b = 100 } = { a: 10, b: 20 } console.log(b);
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
let [h1, y, x] = "一碗周" console.log(h1, y, x, h2); // 一 碗 周
如果直接对数字值/布尔值进行解耦赋值会抛出异常,在对数字值和布尔值进行操作时,其赋值运算符的右边是数值和布尔值,则会先转为对象。
// let [a] = 100; // 抛出异常 描述信息为 TypeError: 100 is not iterable // console.log(a); // 对布尔或者数值进行解耦赋值的话, 需要现将其改变成为对象类型。 let { toString: b } = 1; console.log(b === Number.prototype.toString); // true let { toString: c } = true; console.log(c === Boolean.prototype.toString); // true
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined
和null
无法转为对象,所以对它们进行解构赋值,都会报错。
函数的参数也可以使用解构赋值。示例代码如下所示:
// 使用数组 function f([a, b]) { console.log(a, b); } f([10, 20]) // 10 20 // 使用对象 function fn({ a, b }) { console.log(a, b); } fn({ a: 10, b: 20 }) // 10 20
解耦赋值虽然很方便,但是解析起来并不容易。对于编译器来说,一个式子到底是模式,还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。
由此带来的问题是,如果模式中出现小括号怎么处理。ECMAScript 2015
的规则是,只要有可能导致解构的歧义,就不得使用小括号。
但是,这条规则实际上不那么容易辨别,处理起来相当麻烦。因此,建议只要有可能,就不要在模式中放置小括号。
如下三种情况不能使用小括号
情况一:变量声明语句,示例代码如下所示
// 如下情况全部会报错 let [(a)] = [1]; let {x: (c)} = {}; let ({x: c}) = {}; let {(x: c)} = {}; let {(x): c} = {}; let { o: ({ p: p }) } = { o: { p: 2 } };
上面6个语句都会报错,因为它们都是变量声明语句,模式不能使用小括号。
情况二:作为函数参数
函数参数也属于变量声明,因此不能带有小括号。
// 报错 function f([(z)]) { return z; } // 报错 function f([z,(x)]) { return x; }
情况三:赋值语句的模式
// 全部报错 ({ p: a }) = { p: 42 }; ([a]) = [5];
上面代码将整个模式放在小括号之中,导致报错。
// 报错 [({ p: a }), { x: c }] = [{}, {}];
上面代码将一部分模式放在小括号之中,导致报错。
可以使用小括号的情况只有一种:赋值语句的非模式部分,可以使用小括号。
[(b)] = [3]; // 正确 ({ p: (d) } = {}); // 正确 [(parseInt.prop)] = [3]; // 正确
上面三行语句都可以正确执行,因为首先它们都是赋值语句,而不是声明语句;其次它们的小括号都不属于模式的一部分。第一行语句中,模式是取数组的第一个成员,跟小括号无关;第二行语句中,模式是p,而不是d;第三行语句与第一行语句的性质一致。
变量解耦赋值的用处有很多,下面举几个比较常见的例子。
如果没有解耦赋值交换变量需要借助第三个变量才能完成,示例代码如下所示:
var a = 10, b = 20; var c = a; a = b b = c c = null /// 释放变量 console.log(a, b); // 20 10
借助变量解耦赋值完成 ,示例代码如下所示:
let a = 10, b = 20; [a, b] = [b, a] console.log(a, b); // 20 10
使用这种方式不仅简洁,而且易读,语义清晰。
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。
示例代码如下所示:
// 返回一个数组 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]) { console.log(x, y, z); } f([1, 2, 3]); // 1 2 3 // 参数是一组无次序的值 function fn({x, y, z}) { console.log(x, y, z); } fn({ z: 3, y: 2, x: 1 }); // 1 2 3
解构赋值对提取 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]