前言
年都过去半个月了, 我终于又重新开始更新了. 虽然只是第二篇, 但是我会继续加油努力, 一定不会放弃更新的. 在文章中若有什么不妥或者您有更多建议的话, 欢迎和期待您给我留言, 您的每一个留言都可能成为我进步的助力, 十分感谢. 那就废话不多说直接开始吧.
概念
解构赋值, 顾名思义, 就是解开一个结构, 然后从里面拿出值用来给变量赋值赋值. 所以解构赋值主要是以数据类型来划分的.
数组的解构赋值
var [a,b,c] = [1,2,3];
上述代码算是最简单的数组解构赋值, 其实也可以看做是数据的另一种展示. 比如上述代码与下面的代码其实是一样的.
var a=1,b=2,c=3;
所以解构赋值最主要的作用, 是可以让我们简化提取值的过程.
本质上, 这种写法属于 "模式匹配", 同模式下, 左边的变量就会被赋予对应位置的右边的值. 例如:
- let [a,[[b],c]] = [1,[[2],3]];
- a //1
- b //2
- c //3
并且只要模式相同, 即便部分位置的变量或者值为空, 依旧可以进行匹配.
- let [a, , b] = [1,2,3];
- a //1
- b //3
- let [a,b,c] = [1,2];
- a//1
- b//2
- c//undefind
当解构不成功, 即变量没有得到赋值, 或者直接赋值 undefind 时, 变量的值就会等于 undefind.
当匹配两边的模式相同, 且长度不同时, 此时的解构赋值被称为不完全解构. 虽然叫不完全解构, 但是依旧算解构成功的.
- let [a,b] = [1,2,3];
- a //1
- b //2
上面一直提到一个前提情况, 那就是模式相同, 没错, 这是比较需要注意的一点. 当两边模式不同时, 解构赋值是会报错的.
- let [a] = 1;
- let [b] = false;
- let [c] = {
- };
在不严谨的情况下, 我们可以说, 当两边的数据类型不同时, 解构赋值会出现报错.
只要某种数据结构具有 Iterator 接口, 都可以采用数组形式的解构赋值. Iterator 接口最主要的功能是可以提供遍历命令 for...of 使用, 不难猜测, 其实数组解构赋值是一个将变量遍历循环, 然后一一进行赋值的操作.
- 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
以上是阮一峰大神 ES6 入门里面的实例, 由于个人目前还不清楚具体哪些数据结构具有 Iterator 接口, 所以这里直接搬运一下.
默认值
- let [a=1] = [];
- a//1
解构赋值操作时, 可以设置一个默认值, 若解构赋值操作室, 对应位置上的值为 undefind 时, 将会给变量赋值默认值.
需要注意的是, ES6 内部使用严格相等运算符 (===) 来判断一个位置是否有值, 我一般习惯称它为全等符号. 所以, 与一般的判断不同, 这里只有用于赋值的数组成员的值为 undefined(严格等于 undefined)时, 默认值才会生效.
- let [a=1] = [undefined];
- a//1
- let [b=1] = [null];
- b//null
- let [c=1] = [NaN];
- c//NaN
使用默认值时, 还可以引用解构赋值的其他变量, 但前提是该变量已声明.
- let [a=1,b=a] = [];
- a//1
- b//1
- let [a=b,b=1] = [];
- //ReferenceError: b is not defined
- var [a=b,b=1] = [];
- a//undefined
- b//1
对象的解构赋值
- let {
- a,b
- } = {
- a:'1',b:'2'
- };
- a //'1'
- b //'2'
对象的解构赋值与数组的解构赋值最大的不同之处, 在于数组的解构赋值, 变量的取值是由位置决定的; 而对象的解构赋值, 变量的取值是由属性名来决定的, 只有变量与属性名相同, 才可以取到值.
- let {
- a , b
- } = {
- b : '2' , a : '1'
- };
- a //1
- b //2
- let {
- a
- } = {
- b: '1' , c: '2'
- };
- a//undefined
当变量名与属性名不一致, 却又需要进行解构赋值时, 可以使用变量再进行一次解构赋值.
- let obj = {
- a : '1' , b : '2'
- };
- let {
- a : c , b : d
- } = obj;
- c //'1'
- d //'2'
并且, 在这过程中, 实际被赋值的, 其实是 c 和 d. 而 a 和 b 是模式, 起到类似于一个中介作用, 不会被实际赋值.
- let {
- a : b
- } = {
- a : '1'
- };
- a //ReferenceError: a is not defined
- b //'1'
对象的解构赋值, 与数组的解构赋值一样, 也可以用于嵌套结构的对象.
- let a = {
- b : [
- '1',
- { c : '2' }
- ]
- };
- let {
- b : [
- x ,
- { c }
- ]
- } = a;
- x // '1'
- c // '2'
- b // ReferenceError: b is not defined
此时 b 只是模式, 所以无法被赋值.
默认值
对象的解构赋值也有默认值, 默认值的设置方式与数组相同, 而不是依旧使用对象的内部写法.
- let {
- a = 1
- } = {
- };
- a // 1
- let {
- b : 1
- } = {
- };
- //SyntaxError: Invalid destructuring assignment target
默认值生效的条件与数组的解构赋值相同, 属性值必须严格等于 undefined 才会生效.
- let {
- a = 1
- } = {
- a : undefined
- };
- a //1
- let {
- b = 1
- } = {
- b : null
- };
- b // null
- let {
- c = 1
- } = {
- c : NaN
- };
- c // NaN
在对嵌套的对象使用解构赋值时, 需要注意, 若子对象所在的父属性不存在时, 会报错. 这也是我在工作中, 发现比较常见的一种报错, 还是需要多多注意的. 特别是在使用多层结构的时候, 例如 res.data.id.
- let {
- a: {
- b
- }
- } = {
- c : '1'
- };
- TypeError: Cannot destructure property `b` of 'undefined' or 'null'
在使用对象解构赋值的时候, 如果要对已经声明的变量进行解构赋值, 需要小心.
- let a;
- {
- a
- } = {
- a:1
- };
- //SyntaxError: Unexpected token =
这里是因为 JavaScript 引擎会将 {a} 当做一个代码块, 从而引发语法错误. 所以需要避免将大括号写在行首.
- let a;
- ({
- a
- } = {
- a:1
- });
- a // 1
由于数组的本质是特殊的对象, 因此可以对数组进行对象属性的解构赋值.
- let a = [1, 2, 3];
- let {
- 0 : b, 2: c
- } = a;
- b // 1
- c // 3
第二行代码的 0 和 2 代表的是数组的位置, 可以简单理解为以下代码:
- let a = [1,2,3];
- let b = a[0];
- let c = a[2];
字符串的解构赋值
字符串也可以进行解构赋值, 因为此时字符串被转换成了一个类似数组的对象.
- let [a, b, c, d, e] = 'hello';
- a // 'h'
- b // 'e'
- c // 'l'
- d // 'l'
- e // 'o'
看到这里是不是感觉这个过程有点眼熟, 其实这个过程可以理解为以下代码:
- let x = 'hello';
- a = x[0];
- b = x[1];
- c = x[2];
- ...
数值和布尔值的解构赋值
解构赋值时, 如果等号右边是数值或者布尔值, 则会先转化成对象.
- let {
- toString:a
- } = 123;
- a === Number.prototype.toString // true
- let {
- toString:a
- } = 123;
- a === Boolean.prototype.toString // true
解构赋值的规则是, 若等号右边的值不是对象或者数组, 就会先将其转化成对象. 由于 undefined 和 null 无法转化成对象, 所以对其进行解构赋值时会报错.
- let {
- a:b
- } = undefined;
- //TypeError: Cannot destructure property `a` of 'undefined' or 'null'
- let {
- a:b
- } = null;
- //TypeError: Cannot destructure property `a` of 'undefined' or 'null'
函数参数的解构赋值
- function add({x,y]){
- return x+y;
- }
- add({1,2}); //3
函数 add 的参数表面上为一个数组, 但是在传入参数的那一刻, 数组参数就被解构成了 2 个变量, x 和 y.
函数参数的解构也可以用默认值.
- function move({x=0,y=0} = {} ) {
- return [x,y];
- }
- move({x:1}); // [1,0];
- move({}); // [0,0];
函数参数的解构有另一种写法, 会得出另一种结果.
- function move({x,y} = {x:0,y:0} ) {
- return [x,y];
- }
- move({x:1}); // [1,undefined];
- move({}); // [undefined,undefined];
- move(); // [0,0]
上述的代码时为 move 函数参数设置默认值, 而不是为解构后的 x 和 y 设置默认值, 所以会得出不一样的结果.
圆括号
在使用解构赋值的时候, 圆括号是否使用, 是一个问题.
ES6 的规则中说明, 只要有可能导致解构歧义的, 就不能使用圆括号.
但由于该规则的标准不容易衡量和辨别, 所以一般是尽量不使用圆括号.
不能使用圆括号的情况
变量声明语句
- let [(a)] = [1];
- let {
- x: (c)
- } = {
- };
- // 上述两句代码显示为 undefined
- let ({
- x: c
- }) = {
- };
- let {
- (x: c)
- } = {
- };
- let {
- (x): c
- } = {
- };
- let {
- o: ({
- p: p
- })
- } = {
- o: {
- p: 2
- }
- };
- // 上述四句代码会报错.
上述代码发生这种情况, 主要是因为它们都是变量声明语句, 模式不能使用圆括号.
函数参数 函数参数也属于变量声明, 因此不能带圆括号.
function a( [ ( b ) ] ) { return c; }
赋值语句的模式
- ( {
- a:b
- } ) = {
- a:1
- };
- [ ({
- a:b
- }) , {
- c:d
- } ] = [{
- },{
- }];
无论是将整个模式放入圆括号中, 还是将部分模式放入圆括号中, 都会导致报错.
解构赋值的用途
交换变量的值
- let a = 1;
- let b = 2;
- [a,b] = [b,a]
从函数返回多个值
通过解构赋值, 可以很方便的从数组或者对象里获取多个返回值.
- function arr(){
- return [1,2,3];
- }
- let [a,b,c] = arr();
- // 返回一个数组
- function arr() {
- return {
- a:1,
- b:2
- };
- }
- let { a,b } = arr();
函数参数的定义 解构赋值可以方便地将一组参数与变量名对应起来.
- // 参数是一组有次序的值
- function f([a, b, c]) {
- ...
- }f([1, 2, 3]);
- // 参数是一组无次序的值
- function f({
- a, b, c
- }) {
- ...
- }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);
函数参数的默认值 通过使用解构赋值, 在给函数参数赋予默认值时, 整个代码会显得更加简洁.
- jQuery.Ajax = function (url, {
- async = true,
- beforeSend = function () {},
- cache = true,
- complete = function () {},
- crossDomain = false,
- global = true,
- // 在这里设置默认值
- } = {}) {
- // 这里则是赋值的内容, 若为 undefined, 则使用默认值
- };
遍历 Map 结构 上文说过, 面对拥有 Iterator 接口的对象时, 可以使用解构赋值. 在这里, 我们可以通过解构赋值快速的获取键名和键值.
- const 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
输入模块的指定方法 加载模块时, 需要指定输入哪些方法. 解构赋值使得输入语句非常清晰.
const { SourceMapConsumer, SourceNode } = require("source-map");
总结
在使用解构赋值的时候, 整体感觉上其实就是一个遍历过程的简化. 个人感觉最大的作用是可以将类似逻辑的代码进行过程简化, 从而给代码瘦身.
同时在其中也发现了原文章中的部分细节错误. 例如不能使用圆括号的情况中的第一点, 示例代码中的前两行代码并没有报错, 而是显示 undefined.
然后这里给自己留一个小作业, 是在和朋友聊上述细节错误时发现的一个问题: 为什么 let [(a)] = [1]; 显示 undefined, 而用 [(a)] = [1] 则会显示[1]?
参考文章
ECMAScript 6 入门: 变量的解构赋值 http://es6.ruanyifeng.com/#docs/destructuring
来源: https://juejin.im/post/5c7697c4f265da2dc231f98b