第二章:变量的解构赋值

  在es6 版本前,如果要为多个变量赋不同值,我想是件比较麻烦的事情。但es6 版本新推出了一个新技术那就是今天的主角变量的解构赋值。

  变量解构赋值分为两种方法:数组解构赋值对象解构赋值

  

一:数组解构赋值

1.1 数组解构赋值的基本语法

以前为多个变量赋不同的值只能这样:

let a = 1;
let b = 2;
let c = 3;

但有了解构赋值便可以这样赋,下面例子是数组解构赋值的基本语法

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

所以匹配方式为:左边被声明的变量名一一对应着右边对应数组位置的变量值。也就是说在左边数组中 Index 为 0 的变量a 它的值是 右边数组 index 为 0 的1。

他们的赋值关系是通过数组的下标一一对应的。

再看一个复杂的例子:

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo //
bar //
baz //

代码可分解为这样

let [foo, [[bar], baz]] = [1, [[2], 3]];

// 第一轮分解
foo = 1;
[[bar], baz] = [[2], 3];
// 第二轮分解
baz = 3;
[bar] = [2];
// 第三轮分解
bar = 2; / 所以结果为
// foo = 1
// bar = 2
// baz = 3

由上面的代码可以看出,为一个三维数组解构赋值

第一轮首先将 右边数组的第一个元素赋值到左边数组第一个元素,也就是 foo = 1

第二轮 右边数组第二个元素赋值到左边数组的第二个元素中,因为左边数组第二个元素又是一个数组所以对应的右边数组也应该是一个数组。

它们又会做一次匹配将右边数组的第一个元素赋值到左边数组第一个元素,第二个也是这样(看上面代码第二轮分解)

最后bar 匹配到 2 这个值

上面的次序是个人想出来的并非官方答案

总之,数组解构赋值的方式就是 将右边数组与左边数组的位置一一对应去赋值

1.2 数组不完全的结构,如果没有对应的值而且也没有默认值就会被赋上undefined,如果有默认值则被赋默认值

在上面的例子中,基本就是完全结构的结果(每一个变量都能匹配到对应的值)。而匹配也可以不完全地进行匹配,右边数组的元素个数可以小于左边数组元素个数也可以大于左边元素个数

看如下例子:

let [foo] = [];
let [bar, foo] = [1];

上例第一条语句,右边数组为空,因此foo 无法被赋值,因此foo = undefined,第二条也是如此

再来一个例子:

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

上例中,右边元素个数比左边元素个数多,但左边数组的元素依旧只会匹配回对应下标位置的值

倘若第一个例子有默认值,那么将会是如下效果:

let [foo = 1] = [];            // foo = 1
let [bar, foo = 2] = [1]; // foo = 2

es6 规定了默认值赋值的规则,当右边数组对应左边数组位置的值为 undefined 则会将左边数组对应位置的变量赋上默认值,如果页没有默认值那么就为 undefined

大概意思如下:

let leftArr = [a, b, c];
let value = [1, 2]; for (let i = 0; i < left.length; i++) {
if (value[i] === undefined) {
leftArr[i] = leftArr[i].__default__;
}else {
leftArr[i] = value[i];
}
}

上面的 __default__ 是自己理解的意思,并不真实存在

另外,在判断语句中可以看出,右边数组判断是否存在值是用 === 全等于去判断是否为undefined 的,所以如果遍历到的值为 null 这些不全等于 undefined 的值是不会让左边数组的变量赋上默认值的,而是直接将 null 赋给它,例子:

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

当右边数组对应值全等于undefined 的时候才会触发默认值的赋值。

默认值还可以是一个函数,当没能完成赋值时才会调用函数,例子:

function f() {
return 'aaa';
} let [x = f(), y = f()] = [1]; // x = 1
// y = 'aaa'

默认值可以引用解构赋值的其他变量,但该变量必须已经声明。

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

最后一条语句报错的原因是,y 还没声明就被x 调用作为初始值所以报错

1.3 数组解构,右边的匹配值一定是要有Iterator 接口的对象

例子:

// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

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

使用一些具有 Iterator 接口的表达式也是可以实现数组方式的解构赋值

let [x, y, z] = new Set(['a', 'b', 'c']);
x // "a" 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 //

二:对象解构赋值

2.1 基本语法

我们知道对象实际上是一个无序列表,靠每一个键标识着值得存放位置,那应该怎么声明呢?如下例子:

let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

如果左边是一个对象,那么右边也应该对应是一个对象,上例左边数组中用了 es6 的新特性,就是如果对象中键和值的名字相同,那么就不用写冒号了

用es5 的语法表达如下:

let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
// 所以
foo = "aaa";
bar = "bbb";

假若变量名和键不一样就应该用回传统的 es5 对象语法

所以匹配方式为:右边对象对应左边对象的键,为左边对象对应键的值赋值,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。

和数组一样,对象的解构法则也可以嵌套解构,例子如下:

let obj = {
p: [
'Hello',
{ y: 'World' }
]
}; let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

分解:

let obj = {
p: [
'Hello',
{ y: 'World' }
]
}; let { p: [x, { y }] } = obj; // 第一轮
p = ['Hello', { y: 'World' }];
[x, {y}] = ['Hello', { y: 'World' }] // 第二轮
x = 'hello';
{y} = { y: 'World' }; // 第三轮
y = 'world'; // 所以
// x = 'hello'
// y = 'world'

左边p 键 对应的值是   [x, { y }],所以使得 [x, { y }] 与对应值内部的   ['Hello', { y: 'World' }]  进行数组解构赋值

更深嵌套的例子:

const node = {
loc: {
start: {
line: 1,
column: 5
}
}
}; let { loc, loc: { start }, loc: { start: { line }} } = node;

分解为:

const node = {
loc: {
start: {
line: 1,
column: 5
}
}
}; let { loc, loc: { start }, loc: { start: { line }} } = node; // 第一轮
loc = {
start: {
line: 1,
column: 5
}
}; {start} = {start: {line: 1,column: 5}} { start: { line }} = {start: {line: 1,column: 5}} //第二轮
start = {line: 1,column: 5 }; { line } = {line: 1,column: 5} // 第三轮
line = 1

对象的解构也支持默认值,其规则和数组的解构方式一样,例子:

let {x, y = 5} = {x: 1};
x //
y // let { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"
let {zoo = 3} = {x: null};
zoo // null

2.2 如果解构模式是嵌套的对象,而且子对象的父层引用未能赋上值,那么将会报错。

例子:

let {foo: {bar}} = {baz: 'baz'};

其实可以拆分成这样

let {foo: {bar}} = {baz: 'baz'};

{bar} = undefined;
undefined.bar = undefined;
// 所以报错

2.3 要将事先声明了的变量进行解构赋值,那么就必须将解构语句用括号括住

例子:

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

上面的例子先声明了变量x 再将x 用作解构赋值,但是会报错。

因为浏览器引擎会误以为是两个语句块的赋值,所以报错。

正确的写法应当是将整个解构赋值语句用括号括住,例子:

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

只要不将花括号放在语句句首才不会保证不报错!

2.5 解构赋值允许等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式。

let {} = [true, false];
let {} = 'abc';
let {} = [];

尽管有效但避免应用,因为这样毫无意义

三:解构的一些特殊应用

3.1 可以将一些对象的属性及方法抽取出来

3.1.1 获取对象内部的方法和属性

通过以对象形式的解构,可以将一些常用的对象属性抽取出来保存在变量中,例子:

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

上例中,左边的变量会保存右边 Math 对象中的log, sin, cos 方法,之后调用这些方法可以直接就调用这些变量就可以了

而数组也算是一个对象,所以左边的变量依旧可以保存其内部的方法

3.1.2 获取字符串的每一个字符

通过以数组的形式对字符串进行解构可以达到遍历的效果,例子:

let [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefinednull无法转为对象,所以对它们进行解构赋值,都会报错。

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

3.1.3 利用解构为函数参数传递多个参数

利用解构传递向函数传递多个参数,不仅方便了多个参数的传递,而且还可以为参数赋予默认值,当某个参数没有传递值的时候就可以赋上默认的值,例子:

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

函数 add 参数 [x, y] 匹配到传递的参数值 [1, 2] 所以解构成功

下面是函数参数执行默认值的例子:

注意,如果函数的参数是采用的对象解构模式那么必须向如下格式那样写参数,否则会报错 Uncaught TypeError: Cannot match against 'undefined' or 'null'.

function move({x = 0, y = 0} = {}) {
return [x, y];
} move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

实际上当没有参数传递的时候会执行这条解构语句 {x = 0, y = 0} = {} ,空对象没有可以被匹配的值所以参数会用默认值去初始化自己

当函数有参数的时候会执行  {x = 0, y = 0} = {实际参数} 这条解构语句

下面这种写法还会导致不同的结果:

function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
} move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]

上面代码是为函数move的参数指定默认值,而不是为变量xy指定默认值,当没有参数的时候自然机会执行 {x, y} = { x: 0, y: 0 } 这样的解构语句,所以参数被右边的值初始化了

而传一个空的对象作为参数,因为函数的参数并没有默认值所以为undefined

3.1.4 交换变量的值

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

3.4.5提取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]

3.1.7 输入模块的指定方法

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

四:解构语句中的圆括号使用

4.1 不使用圆括号的情况

  1、 变量声明语句

  在有let 声明的前提下,解构赋值不必有括号

// 全部报错
let [(a)] = [1]; let {x: (c)} = {};
let ({x: c}) = {};
let {(x: c)} = {};
let {(x): c} = {}; let { o: ({ p: p }) } = { o: { p: 2 } };

  2、函数参数

  函数参数里也不必用圆括号

// 报错
function f([(z)]) { return z; }
// 报错
function f([z,(x)]) { return x; }

  3、赋值语句的模式

  赋值语句模式应该将整个解构语句用括号括住而不是一部分括住

// 全部报错

let a;
let b;
({ p: a }) = { p: 42 };
([b]) = [5];
// 正确
let a;
let b;
({ p: a } = { p: 42 });
([b] = [5]);

4.2 可使用圆括号的情况

可使用圆括号的情况就只有一种就是,括号包含的内容不属于模式的一部分,例如:

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

第一句: 元素b 并不是模式的一部分,而是模式里的一个元素

第二句:将赋值语句整句用圆括号包住 符合规定

第三句:同第一句一样情况

【js 笔记】读阮一峰老师 es6 入门笔记 —— 第二章的更多相关文章

  1. 【js 笔记】读阮一峰老师 es6 入门笔记 —— 第一章

      鉴于最近用 vuejs 框架开发项目,其中有很多涉及到 es6 语法不太理解所以便认真地读了一下这本书. 地址:http://es6.ruanyifeng.com/#README 第一章:let ...

  2. 读阮一峰老师 es6 入门笔记 —— 第一章

    鉴于最近用 vuejs 框架开发项目,其中有很多涉及到 es6 语法不太理解所以便认真地读了一下这本书. 地址:http://es6.ruanyifeng.com/#README 第一章:let ,c ...

  3. 关于阮一峰老师es6(第三版)中管道机制代码的理解浅析

    最近正在学习阮一峰老师的es6(第三版)教材,在学到第七章<函数的扩展>中的箭头函数嵌套时,文中提到了一个关于“管道机制”的示例,文中源代码如下: //es6(第三版)教材中的管道机制源代 ...

  4. ES6特性:(阮一峰老师)学习总结

    ES6(阮一峰)学习总结   1.块级作用域的引入 在ES6之前,js只有全局作用域和函数作用域,ES6中let关键字为其引入了块级作用域. { var a = 5; let b = 6; } con ...

  5. react 入门教程 阮一峰老师真的是榜样

    -  转自阮一峰老师博客 React 入门实例教程   作者: 阮一峰 日期: 2015年3月31日 现在最热门的前端框架,毫无疑问是 React . 上周,基于 React 的 React Nati ...

  6. 读阮一峰《ECMAScript 6 入门》小结

    读阮一峰<ECMAScript 6 入门>小结,http://es6.ruanyifeng.com/ 1. ES6简介 Babel 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转 ...

  7. ES6入门笔记

    ES6入门笔记 02 Let&Const.md 增加了块级作用域. 常量 避免了变量提升 03 变量的解构赋值.md var [a, b, c] = [1, 2, 3]; var [[a,d] ...

  8. 读阮一峰对《javascript语言精粹》的笔记,我有疑问。

    <javascript语言精粹>是一本很棒的书籍,其中作者在附录列出了12种他所认为的javascript语言中的糟粕. 我最近开始跟读前端前辈的博客,其中读到了阮一峰的<12种不宜 ...

  9. 监控阮一峰老师的blog

    引言 阮一峰大家基本都认识,很厉害的一个人,经济学博士,文章写得很棒,知识面很广泛,计算机.算法.英语.文采,这是能想到的他的一些标签,他的博客应该算是最受欢迎的博客之一了. 我经常回去看他的博客,但 ...

随机推荐

  1. Linux下设置SSH端口

    SSH 为 Secure Shell的缩写,由 IETF 的网络小组(Network Working Group)所制定:SSH 为建立在应用层基础上的安全协议.SSH 是目前较可靠,专为远程登录会话 ...

  2. mycat安装与配置

    1.安装jdk 测试jdk是否已经安装 [root@node002 ~]# java -version-bash: java: command not found 创建解压目录 [root@node0 ...

  3. Java学习笔记9(面向对象二:this、继承、抽象类)

    就近原则: 类中的方法中的变量和成员变量重名时,调用类的方法时候,生效的是方法中的变量,如果方法中没有定义变量,才会去成员变量中寻找 于是,提出了this关键字,为了区分重名问题 public cla ...

  4. spark 1.6 完全分布式平台搭建

    软件环境: scala-2.11.4.tgz spark-1.6.2-bin-hadoop2.6.tgz 操作步骤: 一.  安装scala 1. 解压scala (tar –zxvf  filena ...

  5. 【数论·欧拉函数】SDOI2008仪仗队

    题目描述 作为体育委员,C君负责这次运动会仪仗队的训练.仪仗队是由学生组成的N * N的方阵,为了保证队伍在行进中整齐划一,C君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如右图 ...

  6. K:二叉树

    相关介绍:  二叉树是一种特殊的树,它的每个节点最多只有两棵子树,并且这两棵子树也是二叉树.由于二叉树中的两棵子树有左右之分,为此,二叉树是有序树. 二叉树的定义:  二叉树是由n(n>=0)个 ...

  7. bridged(桥接模式)、NAT(网络地址转换模式)和host-only(主机模式)-VMware下三种网络配置方式

    VMWare提供了三种工作模式,它们是bridged(桥接模式).NAT(网络地址转换模式)和host-only(主机模式).要想在网络管理和维护中合理应用它们,你就应该先了解一下这三种工作模式. 1 ...

  8. 采用集成的Windows验证和使用Sql Server身份验证进行数据库的登录

    采用集成的Windows验证和使用Sql Server身份验证进行数据库的登录 1.集成的Windows身份验证语法范例 1 string constr = "server=.;databa ...

  9. 【转载】wifi一键配网smartconfig原理及应用

    物联网给我们又提供了一种窃取WiFi密码的好方式:让智能设备主动断线. 同时也提供一种让智能设备连接到恶意WiFi的方式:设备一键配置功能时疯狂广播恶意WiFi的信息. 转自:http://blog. ...

  10. 一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](四)

    前言 上一篇<一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](三)>,我们完成了: * 引用SqlSugar * ...