ES6(三: 变量的解构赋值)

来源:互联网 发布:linux 文件不保存退出 编辑:程序博客网 时间:2024/05/18 09:20

前言

你必须很努力很努力,才能看起来毫不费力

(一)数组的解构赋值

在ES6之前对变量的赋值为:

var a = 1,    b = 2,    c = 3;

ES6 允许我们这样写

const [a, b, c] = [1, 2, 3];// babel编译就是上面的方式

上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。

本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

下面是一些使用嵌套数组进行解构的例子。
解构赋值不仅适用于var命令,也适用于let和const命令。

(1)模式匹配解构

let [foo, [[bar], baz]] = [1, [[2], 3]];console.log(foo); // 1console.log(bar); // 2console.log(baz); // 3

(2)展开…

let [head, ...tail] = [1, 2, 3, 4];console.log(head); // 1console.log(tail); // [2, 3, 4]

(3)如果解构不成功,变量的值就等于undefined 。

const [foo1] = [];const [bar, foo2] = [1];console.log(foo1); // undefinedconsole.log(bar);  // 1console.log(foo2); // undefined

看下babel编译成ES5

var _ref = [],    foo1 = _ref[0];var _ref2 = [1],    bar = _ref2[0],    foo2 = _ref2[1];console.log(foo1); // undefinedconsole.log(bar); // 1console.log(foo2); // undefined

上面代码foo1和foo2都不会解构成功,他们都是undefined。

(4)另一种情况是不完全解构
即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以
成功。

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

(5)对于Set结构,也可以使用数组的解构赋值。

let [x, y, z] = new Set(["a", "b", "c"])x // "a"

事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。

(6)解构赋值允许指定默认值。

const [foo3 = '默认值',foo4 = '不生效默认值'] = [,null];console.log(foo3); // '默认值'console.log(foo4); // null

上面代码foo3对应undefined 所以默认值生效,而foo4对应null,默认值不生效。

原因是: ES6内部使用严格相等运算符(=== ),判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined ,默认值是不会生效的。

(二)对象的解构赋值

(1)解构不仅可以用于数组,还可以用于对象。

const { bar, foo, test2, test } = { foo: "aaa", test: 'haha',bar: "bbb" };console.log(bar);// "bbb"console.log(foo);// "aaa"console.log(test2);// undefinedconsole.log(test);// "haha"

上面代码体现对象的解构与数组有几个重要的不同。
(1)数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属
性没有次序。
(2)变量必须与属性同名,才能取到正确的值。

我们来看下babel编译过后就知道为什么了。

var _foo$test$bar = { foo: "aaa", test: 'haha', bar: "bbb" },    bar = _foo$test$bar.bar,    foo = _foo$test$bar.foo,    test2 = _foo$test$bar.test2,    test = _foo$test$bar.test;console.log(bar); // "bbb"console.log(foo); // "aaa"console.log(test2); // undefinedconsole.log(test); // "haha"

(2)ES6提供当变量名与属性名不一样的写法

const obj = { first: 'hello', last: 'world' };const { first: f, last: l } = obj;console.log(first); // ReferenceError: first is not definedconsole.log(f); // hello

上面代码说明,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,
而不是前者。

让我们来看下babel编译

var obj = { first: 'hello', last: 'world' };var f = obj.first,    l = obj.last;console.log(first); // ReferenceError: first is not definedconsole.log(f); // hello

这里就有个注意点。

采用这种写法时,变量的声明和赋值是一体的。
对于let和const来说,变量不能重新声明,所以一旦赋值的变量以前声明过,就会报错。(// SyntaxError: Duplicate declaration “f”)

(3)数组一样,解构也可以用于嵌套结构的对象并且可以赋默认值

const node = {    loc: {        start: {            line: 1,            column: 5        }    }};const { loc: { start: { line, column: c,test = '默认值' }} } = node;console.log(line); // 1console.log(c); // 5console.log(test); // '默认值'console.log(loc); // error: loc is undefinedconsole.log(start); // error: start is undefined

上面代码loc和start都是模式,本身不会被赋值。而c是column的真正赋值,test本身是一个赋默认值的操作。
当然,生效的条件和数组一样,都是对象的属性值严格等于undefined 。

(4)解构失败,变量的值等于undefined

var {foo} = {bar: 'baz'}foo // undefined

(5)如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。

// 报错var {foo: {bar}} = {baz: 'baz'}

等号左边对象的foo 属性,对应一个子对象。该子对象的bar 属性,解构时会报错。原因很简
单,因为foo 这时等于undefined ,再取子属性就会报错。

看下babel编译就知道为什么了。

var _baz = { baz: 'baz' },    bar = _baz.foo.bar;

(6)已声明的变量用于解构赋值

let x;{x} = {x: 1}; //SyntaxError: Unexpected token =

上面代码的写法会报错,因为JavaScript引擎会将{x} 理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免JavaScript将其解释为代码块,才能解决这个问题。

let x;({x} = {x: 1});console.log(x); // 1

上面代码将整个解构赋值语句,放在一个圆括号里面,就可以正确执行。
关于圆括号与解构赋值的关系,参见下文。

(7)已声明的变量用于解构赋值

对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。

let { log, sin, cos } = Math;

上面代码将Math 对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多。

(三)字符串的解构赋值

字符串也可以解构赋值。
这是因为此时,字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = 'hello';console.log(a); // hconsole.log(b); // econsole.log(c); // l

类似数组的对象都有一个length 属性,因此还可以对这个属性解构赋值。

const { length: len} = 'hello';console.log(len); // 5

(四)数值和布尔值的解构赋值

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

const {toString: s1} = 123;console.log(s1 === Number.prototype.toString); // trueconst {toString: s2} = true;console.log(s2 === Boolean.prototype.toString); // true

上面代码中,数值和布尔值的包装对象都有toString 属性,因此变量s1,s2 都能取到值。

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

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

(五)函数参数的解构赋值

(1)对函数的参数使用解构赋值。

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

上面代码中,函数add 的参数实际上不是一个数组,而是通过解构得到的变量x 和y 。
下面是另一个例子。

[[1, 2], [3, 4]].map(([a, b]) => a + b)// [ 3, 7 ]

(2)对函数参数的解构使用默认值。

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]

上面代码中,函数move 的参数是一个对象,通过对这个对象进行解构,得到变量x 和y 的值。如果解构失
败, x 和y 等于默认值。

下面是对整个参数设置默认值。

undefined 就会触发函数参数的默认值。

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 的参数指定默认值,而不是为变量x 和y 指定默认值,所以会得到与前一种写法不同的结果。

(六)圆括号问题

ES6的规则是,只要有可能导致解构的歧义,就不得使用圆括号。

但是,这条规则实际上不那么容易辨别,处理起来相当麻烦。因此,建议只要有可能,就不要在模式中放置圆括号。

这个不常用,这里不讲了。需要知道自己去了解。哈哈!

(七)用途

乱七八糟讲了很多问题,那么解构赋值到底有什么用途呢。我们来看看!

首先记住,数组解析是有序的匹配,对象解析是无序的。这点很重要!

(1)交换变量的值

[y,x]= [x=1,y=2];console.log(x); //2console.log(y); //1

这是babel的转译

y = x = 1;x = y = 2;console.log(x); //2console.log(y); //1

上面代码交换变量x和y的值,这样的写法不仅简洁,而且易读,语义非常清晰。

(2)从函数返回多个值

函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。

// 返回一个数组const Array = () => [1, 2, 3];const [a, b, c] = Array();console.log(a); // 1console.log(b); // 2console.log(c); // 3// 返回一个对象const Obj =() => {    return {foo: 11, bar: 12}};var { bar,  foo} = Obj();console.log(foo); // 11console.log(bar); // 12

上面代码分别返回数组和对象,然后通过解析非常方便得到对应的值了。

(3)函数参数的定义

解构赋值可以方便地将一组参数与变量名对应起来。

// 参数是一组有次序的值const f1 = ([x, y, z]) => {    console.log(x+';'+y+';'+z);};f1([1, 2, 3]); // 1;2;3// 参数是一组无次序的值const f2 = ({x, y, z}) => {    console.log(x+';'+y+';'+z);};f2({z: 3, y: 2, x: 1}); // 1;2;3

上面代码分别通过有序数组和无序对象定义参数的方式,将变量对应起来。

是不是感觉很常用?对就是react里面

1.首先在父组件中

render= () =>{    const collapsed = this.props.app.collapsed,          handleToggle = this.handleToggle.bind(this);    // header 属性集合    const headerProps = {      collapsed,      handleToggle    };     return (        <div>           <HeaderComponent {...headerProps}/> // 这是展开        </div>    )}

上面代码父组件通过对象展开,直接省去在组件上定义一大堆props的不简洁写法。

2.然后我们看子组件怎么解析

const HeaderComponent = ({collapsed,  handleToggle}) => (    <Header>      <h4 onClick={ () =>(handleToggle()) }>        <Icon          className="trigger"          type={collapsed ? 'menu-unfold' : 'menu-fold'}        />      </h4>    </Header>  );

上面代码子组件是一个stateless组件,通过对象解析赋值,直接省去写 props.xx 的不简洁写法

(4)提取JSON数据

解构赋值对提取JSON对象中的数据,尤其有用。

const jsonData = {    id: 42,    status: "OK",    data: [867, 5309]};const { id, status, data: number } = jsonData;console.log(id, status, number);// 42, OK, [867, 5309]

上面代码可以快速提取JSON数据的值。

(5)函数参数的默认值

jQuery.ajax = function (url, {    async = true,    beforeSend = function () {},    cache = true,    complete = function () {},    crossDomain = false,    global = true,// ... more config    }) {// ... do stuff};

指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || ‘default foo’; 这样的开关赋予默认值的语句。

(6)遍历Map结构

任何部署了Iterator接口的对象,都可以用for…of 循环遍历。 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 worl

如果只想获取键名,或者只想获取键值,可以写成下面这样

// 获取键名for (let [key] of map) {// ...}// 获取键值for (let [,value] of map) {// ...}

(7)输入模块的指定方法

const { SourceMapConsumer, SourceNode } = require("source-map");import React, { PropTypes } from 'react';