深入浅出讲解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 相关文章推荐
Javascript实例教程(19) 使用HoTMetal(7)
Dec 23 Javascript
javascript 模式设计之工厂模式详细说明
May 10 Javascript
js实现的仿新浪微博完美的时间组件升级版
Dec 20 Javascript
JavaScript 函数replace深入了解
Mar 14 Javascript
使用JSLint提高JS代码质量方法分享
Dec 16 Javascript
Javascript实现简单的富文本编辑器附演示
Jun 16 Javascript
JS实现的用来对比两个用指定分隔符分割的字符串是否相同
Sep 19 Javascript
JavaScript数据结构之二叉树的查找算法示例
Apr 13 Javascript
react.js使用webpack搭配环境的入门教程
Aug 14 Javascript
javascript中关于类型判断的一些疑惑小结
Oct 14 Javascript
Element InputNumber计数器的使用方法
Jul 27 Javascript
vue2.0 watch里面的 deep和immediate用法说明
Oct 30 Javascript
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
WML,Apache,和 PHP 的介绍
2006/10/09 PHP
PHP中几种常见的超时处理全面总结
2012/09/11 PHP
php支付宝在线支付接口开发教程
2016/09/19 PHP
判定是否原生方法的JS代码
2013/11/12 Javascript
js+html5实现canvas绘制椭圆形图案的方法
2016/05/21 Javascript
谈谈对JavaScript原生拖放的深入理解
2016/09/20 Javascript
微信js-sdk上传与下载图片接口用法示例
2016/10/12 Javascript
浅谈Angular.js中使用$watch监听模型变化
2017/01/10 Javascript
vue学习笔记之v-if和v-show的区别
2017/09/20 Javascript
jQuery UI实现动画效果代码分享
2018/08/19 jQuery
这样回答继承可能面试官更满意
2019/12/10 Javascript
vue cli3适配所有端方案的实现
2020/04/13 Javascript
Vue 解决通过this.$refs来获取DOM或者组件报错问题
2020/07/28 Javascript
基于vue--key值的特殊用处详解
2020/07/31 Javascript
微信小程序实现聊天室
2020/08/21 Javascript
js实现复制粘贴的两种方法
2020/12/04 Javascript
关于Js中new操作符的作用详解
2021/02/21 Javascript
[42:32]VP vs RNG 2019国际邀请赛淘汰赛 败者组 BO3 第一场 8.21.mp4
2020/07/19 DOTA
用Python实现服务器中只重载被修改的进程的方法
2015/04/30 Python
深入解析神经网络从原理到实现
2019/07/26 Python
浅谈python3中input输入的使用
2019/08/02 Python
python二进制读写及特殊码同步实现详解
2019/10/11 Python
利用keras加载训练好的.H5文件,并实现预测图片
2020/01/24 Python
Django封装交互接口代码
2020/07/12 Python
Django vue前后端分离整合过程解析
2020/11/20 Python
使用Python+Appuim 清理微信的方法
2021/01/26 Python
仿酷狗html5手机音乐播放器主要部分代码
2013/05/15 HTML / CSS
逼真的HTML5树叶飘落动画
2016/03/01 HTML / CSS
全球最大运动品牌的男装、女装和童装官方库存商:A&A Sports
2021/01/17 全球购物
雪山饭庄的创业计划书范文
2014/01/18 职场文书
旅游业大学生创业计划书
2014/01/31 职场文书
关于青春的演讲稿500字
2014/08/22 职场文书
首席执行官观后感
2015/06/03 职场文书
上甘岭观后感
2015/06/10 职场文书
总结会主持词
2015/07/02 职场文书
MySQL三种方式实现递归查询
2022/04/18 MySQL