深入浅出讲解ES6的解构


Posted in Javascript onAugust 03, 2016

什么是解构?

解构与构造数据截然相反。 例如,它不是构造一个新的对象或数组,而是逐个拆分现有的对象或数组,来提取你所需要的数据。

ES6使用了一种新模式来匹配你想要提取的数值, 解构赋值就是采用了这种模式。 该模式会映射出你正在解构的数据结构,只有那些与该模式相匹配的数据,才会被提取出来。

被解构的数据项位于赋值运算符 = 的右侧,可以是任何数组和对象的组合,允许随意嵌套。用于给这些数据赋值的变量个数不限。

数组解构

数组解构 使用一个数组作为一个数据项,你可以根据 数组模式 (用于从数组中匹配你所需要的数值)从这个数组里面提取数值给一个或者多个变量赋值。

数组模式 是根据数值的位置来鉴别哪些值是你想要提取的。它必须能精确地映射数组的结构,来要让数组模式中的每个变量都被赋上 被解构数组中 位置与之相对应的值。

举几个例子来帮助我们理解吧:

数组模式示例

把数组中所有的数值赋给一个个单独的变量

// 设置数组
  const avengers = ['Tony Stark', 'Steve Rogers', 'Natasha Romanoff'];

  // 把数组解构赋值给变量。数组模式位于赋值运算符 `=` 的左侧,被结构的数组在
  // 其右侧。
  const [ironMan, cap, blackWidow] = avengers;

  // ironMan = 'Tony Stark' 
  // cap = 'Steve Rogers'
  // blackWidow = 'Natasha Romanoff'

  // 输出 ironMan:
  ironMan;

提取除第一个外的所有数值

const avengers = ['Tony Stark', 'Steve Rogers', 'Natasha Romanoff'];

  // 我们不用用到Tony
  const [, cap, blackWidow] = avengers;

  // ironMan = Error: undefined 
  // cap = 'Steve Rogers'
  // blackWidow = 'Natasha Romanoff'

  // 输出 cap:
  cap;

提取除第二个外的所有数值

const avengers = ['Tony Stark', 'Steve Rogers', 'Natasha Romanoff'];

  // cap 缺失
  const [ironMan, , blackWidow] = avengers;

  // ironMan = 'Tony Stark' 
  // cap = Error: undefined
  // blackWidow = 'Natasha Romanoff'

  // 输出 blackWidow:
  blackWidow;

提取除最后一个外的所有数值

const avengers = ['Tony Stark', 'Steve Rogers', 'Natasha Romanoff'];

  // ironMan vs cap
  const [ironMan, cap] = avengers;

  // ironMan = 'Tony Stark' 
  // cap = 'Steve Rogers'
  // blackWidow = Error: undefined

  // 输出 blackWidow:
  ironMan;

嵌套数组

这种匹配模式也支持嵌套数组,只要保证赋值运算符 = 左侧的数组模式与右侧的数组结构相匹配即可。再次说明一下,= 左边的变量都会被赋上 = 右侧数组中位置与之相对应的值。 无论你怎么深层次地嵌套,仍可以对它们进行解构。

解构嵌套的数组

// Destructuring Nested Arrays
  const avengers = [
            'Natasha Romanoff', 
            ['Tony Stark', 'James Rhodes'], 
            ['Steve Rogers', 'Sam Wilson']
          ];

  // Avengers and their partners
  const [blackWidow, [ironMan, warMachine], [cap, falcon]] = avengers;

  // blackWidow = 'Natasha Romanoff'
  // ironMan = 'Tony Stark'
  // warMachine = 'James Rhodes'
  // cap = 'Steve Rogers'
  // falcon = 'Sam Wilson'

  // Output warMachine:
  warMachine;

从深层嵌套的数组中提取一个值

// 从该数组中提取 Pepper Potts
  const avengers = [
            'Natasha Romanoff', 
            [['Tony Stark', 'Pepper Potts'], 'James Rhodes'], 
            ['Steve Rogers', 'Sam Wilson']
          ];

  // Destructure
  const [ , // 跳过 'Natasha Romanoff'
       [[ , // 跳过 'Tony Stark'
       hera // Pepper Potts 赋值给变量 'hera'
     ]]] = avengers;

  // 请注意:你也可以这样写
  // const [, [[, hera ]]] = avengers;

  // 输出 hera:
  hera;

  // hera = 'Pepper Potts'

运用rest操作符捕获所有剩余项

如果你想要获取特定的数组项,并且把剩余的项归在一个数组,那么你可以这样运用 rest操作符 来解构:

// 通过rest操作符解构
  const avengers = ['Natasha Romanoff', 'Tony Stark', 'Steve Rogers'];

  const [blackWidow, ...theOthers] = avengers;

  theOthers;
  // blackWidow = 'Natasha Romanoff'
  // theOthers = ['Tony Stark', 'Steve Rogers']

  // 输出 theOthers:
  theOthers;

对象解构

对象解构就更神奇了,尤其是当你需要从一个复杂的、深层嵌套的对象中取值时,其作用更加明显。重申一下,对象解构与数组解构用的是同样的规则(即在赋值运算符左侧创建一个 对象模式, 使它的变量位置与 = 右侧对象的值位置相匹配)。

在对象解构中,你需要指明那些需要被提取值的属性名称,以及将要被赋值的变量名。跟数组解构一样,我们需要在赋值运算符左边先创建一个对象模式来映射被解构的对象。

尽管在这种情况下,我们想要提取的是 对象属性的值 (如:我们从 { prop: value } 中提取 value)。相应地,我们的对象模式必须有一个变量,这个变量的位置要跟我们即将提取的属性值所在的位置一致。

简单示例

提取一个简单的对象属性值

我们可以这样做,来将对象 { ironMan: 'Tony Stark' } 的属性 ironMan 的值 'Tony Stark' 赋值给变量 a

//解构对象的属性值,赋给单个变量 `a`:
 const { ironMan: a } = { ironMan: 'Tony Stark' };

 // 输出 a:
 a;  // a = 'Tony Stark '

提取多个属性值

我们只要拓展相同的模式,就可以从一个对象中提取多个属性值,如下:

// Setup our object
 const avengers = {
  ironMan: 'Tony Stark', 
  cap: 'Steve Rogers', 
  blackWidow: 'Natasha Romanoff'
 };

 // Destructure object to individual variables
 const { 
  ironMan: a, 
  cap: b, 
  blackWidow: c 
 } = avengers;

 // a = 'Tony Stark '
 // b = 'Steve Rogers'
 // c ='Natasha Romanoff'

 // Output a:
 a;

观察一下这个解构模式是怎么确切地匹配 被解构对象 的。

嵌套的对象解构

像解构嵌套数组一样,我们可以对嵌套对象进行解构,不管它的层级多深。

// Setup our object
 const avengers = {
  blackWidow: 'Natasha Romanoff',
  ironManCharacters: {
   couple: {
    ironMan: 'Tony Stark', 
    hera: 'Pepper Potts',
    },
    partner: {
       warMachine: 'James Brodie'
    }
  },
  capCharacters: {
   cap: 'Steve Rogers', 
   partner: {
    falcon: 'Sam Wilson'
   }
  }
 };

 // Destructure object to individual variables
 const { 
  blackWidow: a,
  ironManCharacters: { 
   couple: {
    ironMan: b,
    hera: c
  },
   partner: {
    warMachine: d
   }
  },
  capCharacters: {
   cap: e,
   partner: {
    falcon: f
   }
  }
 } = avengers;

 // a = 'Natasha Romanoff'
 // b = 'Tony Stark '
 // c = 'Pepper Potts'
 // d = 'James Brodie'
 // e = 'Steve Rogers'
 // f = 'Sam Wilson'

 // Output a:
 a;

给赋值的变量命名

当然,把变量名设为诸如 a, b, c 之类,是很糟糕的,变量名称应该是有意义的。

冗长式命名

// Setup our object
 const avengers = {
  ironMan: 'Tony Stark', 
  cap: 'Steve Rogers', 
  blackWidow: 'Natasha Romanoff'
 };

 // Destructure object to individual variables with meaningful names
 const { 
  ironMan: ironMan,
  cap: cap, 
  blackWidow: blackWidow
 } = avengers;

 // blackWidow = 'Natasha Romanoff'
 // ironMan = 'Tony Stark '
 // cap = 'Steve Rogers'

 // Output blackWidow:
 blackWidow;

这种做法比上面用 a,b,c 命名好,但是仍然可以完善。 { ironMan: ironMan } 看起来有点丑而且不直观。

语法上命名捷径

如果你要把一个对象的属性值赋给一个变量,该变量的名称跟对象的属性名称一样,那么在 = 左侧的赋值模式里面,你只需要简单地写属性名即可,如下:

// Setup our object
 const avenger = {
  ironMan: 'Tony Stark'
 };

 // Destructure object to individual variables with meaningful names
 const { 
  ironMan  // equivalent to 'ironMan: ironMan'
 } = avenger;

 // ironMan = 'Tony Stark '

 // Output ironMan:
 ironMan;

由于 被解构的对象属性名称 跟 被赋值的变量名称 相同,我们只需要把名称列出来一次即可。

语法简洁

我们稍微重新修整下前面的代码,就可以使它们看起来更加简洁明了:

// Setup our object
 const avengers = {
  ironMan: 'Tony Stark', 
  cap: 'Steve Rogers', 
  blackWidow: 'Natasha Romanoff'
 };

 // Destructure object to individual variables with meaningful names
 const { ironMan, cap, blackWidow } = avengers;

 // Output ironMan:
 ironMan;

从对象中提取一个深层嵌套的属性

当我们要提取一个深层嵌套的对象属性时,事情就更有趣了:

// Setup our object
const avengers = {
  blackWidow: 'Natasha Romanoff',
  ironManCharacters: {
   couple: {
     ironMan: 'Tony Stark',
     hera: 'Pepper Potts',
   },
   partner: {
     warMachine: 'James Brodie'
   }
  },
  capCharacters: {
   cap: 'Steve Rogers',
   partner: {
     falcon: 'Sam Wilson'
   }
  }
};

// Destructure a deeply nested object
const { ironManCharacters: { couple } } = avengers;

// couple = {
//  ironMan: 'Tony Stark', 
//  hera: 'Pepper Potts',
// }

// Output couple:
couple;

等等,你是怎么阅读这段代码的?couple 这个变量又是怎么被定义的呢?

通过这样拆分,我们就可以看出赋值运算符 = 左侧是被解构对象的一个映射:

const avengers = {
  ironManCharacters: {
   couple: {
     ironMan: 'Tony Stark', 
     hera: 'Pepper Potts',
   }
  }
};

const { 
  ironManCharacters: { 
   couple 
  }
} = avengers;

// Output couple:
couple;

仅仅使用 const { couple } = avengers; 并没有办法提取出 couple 的值。只有把要提取的对象属性的 位置和名称映射出来,JS 编译器才能得到相应的信息,沿着对象的所有属性往下查找,并准确地提取我们想要的值。

这里也要注意到 couple 用了语法捷径给变量命名,实际上是这样的:

const { 
  ironManCharacters: { 
   couple: couple
  }
} = avengers;

couple 就是这样被定义的,它的值就是对象 avengers 中属性名为 couple 的值。

给对象的属性解构赋值

到目前为止,我们都是解构对象的值来给单个的变量赋值,其实还可以给另一个对象的属性赋值。

const avengers = {
 blackWidow: 'Natasha Romanoff',
 ironManCharacters: {
  couple: {
   ironMan: 'Tony Stark',
   hera: 'Pepper Potts'
  }
 }
};

const ironManProperties = {
 family: {}
};

({
 ironManCharacters: {
  couple: ironManProperties.family
 }
} = avengers);

ironManProperties.family
// ironManProperties.family = {
//  ironMan: 'Tony Stark',
//  hera: 'Pepper Potts'
// }

// Output ironManProperties.family:
ironManProperties.family;

在这里我们把 ironManCharacters.couple 的值赋给了 ironManProperties.family 这个属性,这里有两点需要说明一下:

1.解构赋值必须被包含在 圆括号 内

当我们在对一个已存在的变量(如上面例子中的 ironManProperties)进行解构时,一定要这样做,而不是去声明一个新的变量。

2.模式仍然相匹配

{ ironManCharacters: { couple... } } 与对象 avengers 中的 ironManCharacters 相匹配。这样就能如你所愿,从 avengers 对象中提取出 ironManCharacters.couple 的值了。但是现在,couple 后面放置了一个新的对象ironManProperties 和它的属性 family,其实被赋值的就是这个对象的属性ironManProperties.family了。

当你尝试把这种情况解释清楚时,是否还有所困惑呢?在jsfiddle里面尝试上面的代码,一切就明了了。

如果你不清楚自己为什么要这样做,请参考下一篇文章的例子。这些例子会告诉你,为什么采用这种模式来解构API调用的 JSON 对象,让你领略解构的神奇之处!

默认值

解构时,你还可以给变量指定一个默认值:

// Setup our object
 const avengers = {
  ironMan: 'Tony Stark', 
  cap: 'Steve Rogers', 
  blackWidow: 'Natasha Romanoff'
 };

 // Destructure using defaults
 const { ironMan, cap, blackWidow, theHulk='Bruce Banner' } = avengers;

 // ironMan = 'Tony Stark' 
 // cap = 'Steve Rogers'
 // blackWidow = 'Natasha Romanoff'
 // theHulk = 'Bruce Banner'

 // Output blackWidow:
 blackWidow;

解构时要避免出现这些问题

解构赋值时没有使用 const, let, var

在讲到对 对象属性 进行解构赋值时就已经提及了这一点,但这里还是有必要再重申一下,让大家有个深刻的印象。

不能对已经声明的变量进行解构

也就是说,你只能在对变量解构赋值的同时声明变量。

// Setup our object
  const avengers = {
   ironMan: 'Tony Stark', 
   cap: 'Steve Rogers', 
   blackWidow: 'Natasha Romanoff',
   theHulk: 'Bruce Banner'
  };

  // Valid destructuring
  const { ironMan } = avengers;

  let { cap } = avengers;

  var { blackWidow } = avengers;

  // Invalid destructuring
  let theHulk;

  { theHulk } = avengers;
  // Error

  // Output theHulk:
  theHulk;

为何不能对一个已经声明的变量进行解构呢?那是因为这时如果你使用了花括号 { ,JavaScript会认为你是在声明一个 block

解决的办法就是把整个解构赋值用一对 圆括号 括起来。

如何对一个已声明的变量进行解构赋值

// Setup our object
  const avengers = {
   ironMan: 'Tony Stark', 
   cap: 'Steve Rogers', 
   blackWidow: 'Natasha Romanoff',
   theHulk: 'Bruce Banner'
  };

  // A valid Hulk
  let theHulk;

  ({ theHulk } = avengers);
  // theHulk = 'Bruce Banner'

  // Output theHulk:
  theHulk;

现在我们不是以花括号开头,所以JS不会认为我们是在声明一个 block ,这样就可以达到预期的解构结果。

直接返回一个被解构的值

在没有先声明一个接下来要被返回的变量时,就直接返回一个被解构的值,这样是无法达到预期效果的。例如,下面的代码中,返回的将是整个 ironMan对象,而不是预期要的值 Tony Stark

// Note: this doesn't work!
 function getTonyStark(avengers){
  return { ironMan: { realName } } = avengers;
  // return the avengers object, not the realName value
 }

 const avengers = {
  ironMan: {
   realName: 'Tony Stark'
  }
 };

 const tonyStark = getTonyStark(avengers);

 // tonyStark = {
 //  ironMan: {
 //   realName: 'Tony Stark'
 //  }
 // };

 // Output tonyStark:
 tonyStark;

要从一个被解构的对象中提取值,必须先把它赋值给一个变量,然后再把这个变量返回,如下代码所示:

// Note: this DOES work!
 function getTonyStark(avengers){
  const { ironMan: { realName } } = avengers;
  return realName;
 }

 const avengers = {
  ironMan: {
   realName: 'Tony Stark'
  }
 };

 const tonyStark = getTonyStark(avengers);

 // tonyStark = 'Tony Stark'

 // Output tonyStark:
 tonyStark;

这种把赋值和返回分成两行代码的做法实在惹人厌烦,代码丑陋,也显得没必要。但很不幸,JavaScript就是这样工作的----你必须先把解构的值赋给一个变量,然后再把它返回,两步必须分开做。

但是,没有说我们只是说分开做,并没有说一定要摆成两行代码,所以像下面这样写成一行,也是能达到预期效果的:

function getTonyStark(avengers){
  return ({ ironMan: { realName } } = avengers) && realName;
 }

 const avengers = {
  ironMan: {
   realName: 'Tony Stark'
  }
 };

 const tonyStark = getTonyStark(avengers);
 // tonyStark = 'Tony Stark'

 // Output tonyStark:
 tonyStark;

由于JavaScript的 _short-circuit_ 逻辑操作符 (&& and ||) 会基于第一个操作数的值来返回第二个操作数的值,所以这种写法能够达到预期效果。这里,第一个操作数是解构赋值表达式,把值赋给 realName。而 realName 也就是第二个操作数,所以它的值最终被返回。

这样做不是最佳的,但是能实现。在追求代码简短的同时,一定要注意代码的可读性。

总结
本文深入讲解了 解构赋值 的主要原则。解构不仅能减少你的代码量,还能从根本上改变你的编码方式。用的越多,你就会发现越多塑造数据和函数的方式,这些实现方式在过去几乎是不可能的。希望本文对大家学习ES6有所帮助。

Javascript 相关文章推荐
extjs grid设置某列背景颜色和字体颜色的方法
Sep 03 Javascript
(jQuery,mootools,dojo)使用适合自己的编程别名命名
Sep 14 Javascript
修改jquery.lazyload.js实现页面延迟载入
Dec 22 Javascript
js调用webservice中的方法实现思路及代码
Feb 25 Javascript
JavaScript 函数参数是传值(byVal)还是传址(byRef) 分享
Jul 02 Javascript
jquery采用oop模式class类的使用示例
Jan 22 Javascript
js完整倒计时代码分享
Sep 18 Javascript
Angular.js中$apply()和$digest()的深入理解
Oct 13 Javascript
MUI  Scroll插件的使用详解
Apr 13 Javascript
基于js实现逐步显示文字输出代码实例
Apr 02 Javascript
vue+Element中table表格实现可编辑(select下拉框)
May 21 Javascript
vue 计算属性和侦听器的使用小结
Jan 25 Vue.js
JS模拟实现方法重载示例
Aug 03 #Javascript
jQuery数组处理函数整理
Aug 03 #Javascript
功能强大的Bootstrap组件(结合js)
Aug 03 #Javascript
AngularJS基础 ng-submit 指令简单示例
Aug 03 #Javascript
一个简单的JavaScript Map实例(分享)
Aug 03 #Javascript
AngularJS教程 ng-style 指令简单示例
Aug 03 #Javascript
js判断数组key是否存在(不用循环)的简单实例
Aug 03 #Javascript
You might like
php 执行系统命令的方法
2009/07/07 PHP
利用浏览器的Javascript控制台调试PHP程序
2014/01/08 PHP
关于事件mouseover ,mouseout ,mouseenter,mouseleave的区别
2015/10/12 Javascript
jQuery简单实现仿京东分类导航层效果
2016/06/07 Javascript
通过BootStrap实现轮播图的实际应用
2016/09/26 Javascript
jQuery实现右键菜单、遮罩等效果代码
2016/09/27 Javascript
JavaScript表单验证完美代码
2017/03/02 Javascript
详解axios在node.js中的post使用
2017/04/27 Javascript
bootstrap+jQuery实现的动态进度条功能示例
2017/05/25 jQuery
详解nodejs模板引擎制作
2017/06/14 NodeJs
为什么我们要做三份 Webpack 配置文件
2017/09/18 Javascript
vue.js+ElementUI实现进度条提示密码强度效果
2020/01/18 Javascript
如何使用Javascript中的this关键字
2020/05/28 Javascript
[02:57]DOTA2亚洲邀请赛小组赛第四日 赛事回顾
2015/02/02 DOTA
Python获取脚本所在目录的正确方法
2014/04/15 Python
举例讲解Python设计模式编程的代理模式与抽象工厂模式
2016/01/16 Python
Django 导出 Excel 代码的实例详解
2017/08/11 Python
如何使用 Pylint 来规范 Python 代码风格(来自IBM)
2018/04/06 Python
解决pycharm安装后代码区不能编辑的问题
2018/10/28 Python
python利用ffmpeg进行录制屏幕的方法
2019/01/10 Python
在Python中使用Neo4j的方法
2019/03/14 Python
python隐藏类中属性的3种实现方法
2019/12/19 Python
Python面向对象原理与基础语法详解
2020/01/02 Python
Django ModelForm操作及验证方式
2020/03/30 Python
Python xlwt模块使用代码实例
2020/06/10 Python
pytest fixtures装饰器的使用和如何控制用例的执行顺序
2021/01/28 Python
Sisley法国希思黎中国官网:享誉全球的奢华植物美容品牌
2019/06/30 全球购物
说说你所熟悉或听说过的j2ee中的几种常用模式?及对设计模式的一些看法
2012/05/24 面试题
2014年保洁工作总结
2014/11/24 职场文书
男方婚礼答谢词
2015/01/20 职场文书
2015学校师德师风工作总结
2015/04/22 职场文书
2015年学校心理健康教育工作总结
2015/05/11 职场文书
教师节表彰会主持词
2015/07/06 职场文书
谢师宴学生答谢词
2015/09/30 职场文书
Python常用配置文件ini、json、yaml读写总结
2021/07/09 Python
Nginx实现会话保持的两种方式
2022/03/18 Servers