ES6解构赋值详解
2017-08-02 16:18
537 查看
文章转载自:http://www.zhufengpeixun.cn/article/167
解构赋值(destructuring assignment)语法是一个 Javascript 表达式,这种语法能够更方便的提取出 Object 或者 Array 中的数据。这种语法可以在接受提取的数据的地方使用,比如一个表达式的左边。有明确的语法模式来告诉我们如何使用这种语法提取需要的数据值。
解构 Object:
解构能帮助更好地处理方法返回的对象:
Array 的解构
解构数组,对所有可遍历的值有效。
同样的,解构数组也能帮助我们更好地处理函数返回值:
而且,你也可以忽略你不感兴趣的返回值:
你也可以忽略全部返回值,不过似乎没啥用:
当解构一个数组时,可以使用剩余模式(拓展语句,Spread operator),将数组剩余部分赋值给一个变量。
解构可以在下面这些情景中使用,只展现了数组模式的演示,对象模式也是如此。
也可以在 for-of 循环中使用:
在解构中,有下面两部分参与:
Destructuring source: 解构的源,将要被解构的数据,比如解构赋值表达式的右边部分。
Destructuring target: 解构的目标,比如解构复制表达式的左边部分。
解构的目标可以是下面三个的任意一个:
赋值对象,Assigment Patterns。例如 x . 赋值对象通常来说是一个变量。但是在解构赋值中,你有更多的选择,稍后会讲到。
对象模型,Object Patterns。比如:{first: «pattern», last: «pattern»}
数组模型,Object Patterns。比如:[«pattern», «pattern»]
可以任意嵌套模型,而且是可以非常任性的嵌套。
在一个表达式 pattern = someValue 中,pattern是如何访问 someValue 的呢?
Object patterns 将 value 转换成 Object
在访问属性之前,object pattern 将解构的源数据(destructuing source)转换成对象。
在这个过程中,强制转换成对象的过程不是通过 Object() 方法,而是通过内置的操作方法 toObject()。这两个操作处理undefined 和null的方式不太一样。
Object()方法将原始类型值转换成包装类型对象(wrapper object),原来的值原封不动。
也会将undefined 和 null 转换成一个空的对象。
对比之下,当遇到 undefined 和null的时候,toObject()方法则会抛出一个错误。所以下面的解构是失败的:
因此,你可以使用空对象模型 {} 来检查一个值是否被强制转换成了一个对象。正如前面提到的规则,undefined和 null 将会抛出错误
表达式两边的括号是必须的,因为在 JavaScript 中,声明不能以花括号开始。
数组解构使用一个迭代器来获取数据源中的元素。因此,你可以对任何可以遍历的值使用数组解构。
字符串是可遍历的:
我们无法通过索引访问 Set 中的元素,但是可以通过迭代器。所以,数组解构能够在 Sets 上工作:
Set的迭代器总是按照元素插入的顺序将元素返回,所以上述的解构返回的结果总是相同的。
如果一个值有一个 key 为 Symbol.iterator 的方法,这个方法返回的是一个对象,那么这个值是可以遍历的。如果被解构的值不能遍历的,那么“数组解构”会抛出一个 TypeError 错误。
可以用一个空的数组模型 [] 来检查值是不是可遍历的:
默认值是可选的,在数据源中找不到对应的值时,如果设置了默认值,则匹配这个默认值作为匹配结果,否则返回 undefined。
当解构模式有匹配结果,且匹配结果是 undefined 时,也会使用默认值作为返回结果:
默认值是根据需要计算出来的
也就是说下面的解构:
相当于:
使用 console.log() 可以观察到:
在第二个解构中,默认值没有触发,并且 log() 没有被调用。
默认值可以引用模式中的任何变量,包括相同模式中的其他变量:
但是,变量的顺序很关键,从左到右,先声明的变量不能引用后声明的变量,也就是左边的不能引用右边的。
patterns 的默认值
到目前为止,我们所看到的都是模式中变量的默认值,我们也可以为模式设置默认值。
如果整个模式没有匹配结果,则使用 {} 作为数据源来匹配。
上面的例子中,x 为 undefined 可能还是不够直观。看下面这个例子:
如果属性值是一个变量,和属性的 key 相同,就可以忽略这个 key:
如果把表达式放入方括号中,可以用这个表达式声明属性的键:
在解构的过程中可以跳过一些元素:
剩余运算符可以将一个可遍历对象中剩余的元素提取到一个数组中。如果这个运算符在数组模式中使用,运算符必须放在最后:
要注意的时,拓展运算符(spread operator)与剩余操作符有着相同的语法 - 三个点。但是它们之间有区别:前者将数组变成多个元素;后者则用来解构和提取数据,多个元素压缩成一个元素。
如果运算符找不到任何元素,将会匹配一个空的数组,永远不会返回 undefined 或者 null。例如:
操作符不一定非要是一个变量,也可以使用模式:
在使用解构的时候,有两点要考虑清楚:
不能使用大括号作为声明语句的开头;
在解构的过程中,可以申明变量或者分配给变量,但是不能同时这么做;
在 for-of 中使用解构:
使用解构交换两个变量的值:
或者:
还可以分割数据:
处理方法返回的数组更加方便:
要注意的一点是:exec 等一些方法可能会返回 null,导致程序抛出错误TypeError,此时需要添加一个默认值:
解构赋值(destructuring assignment)语法是一个 Javascript 表达式,这种语法能够更方便的提取出 Object 或者 Array 中的数据。这种语法可以在接受提取的数据的地方使用,比如一个表达式的左边。有明确的语法模式来告诉我们如何使用这种语法提取需要的数据值。
Object 的解构
解构 Object:const obj = { first: 'Jane', last: 'Doe' }; const {first: f, last: l} = obj; // f = 'Jane'; l = 'Doe' // {prop} is short for {prop: prop} const {first, last} = obj; // first = 'Jane'; last = 'Doe'
解构能帮助更好地处理方法返回的对象:
const obj = { foo: 123 }; const {writable, configurable} = Object.getOwnPropertyDescriptor(obj, 'foo'); console.log(writable, configurable); // true true
Array 的解构
解构数组,对所有可遍历的值有效。
let foo = ["one", "two", "three"]; let [one, two, three] = foo; console.log(one); // "one" console.log(two); // "two" console.log(three); // "three" const iterable = ['a', 'b']; const [x, y] = iterable; // x = 'a'; y = 'b' [x, y] = iterable; // window.x = 'a'; window.y = 'b';
同样的,解构数组也能帮助我们更好地处理函数返回值:
const [all, year, month, day] = /^(\d\d\d\d)-(\d\d)-(\d\d)$/ .exec('2999-12-31');
而且,你也可以忽略你不感兴趣的返回值:
function f() { return [1, 2, 3]; } let [a, , b] = f(); console.log(a); // 1 console.log(b); // 3
你也可以忽略全部返回值,不过似乎没啥用:
[,,] = f();
当解构一个数组时,可以使用剩余模式(拓展语句,Spread operator),将数组剩余部分赋值给一个变量。
let [a, ...b] = [1, 2, 3]; console.log(a); // 1 console.log(b); // [2, 3]
在什么地方可以使用解构
解构可以在下面这些情景中使用,只展现了数组模式的演示,对象模式也是如此。// 变量声明: const [x] = ['a']; let [x] = ['a']; var [x] = ['a']; // 赋值: 下面这种情况将会在全局变量上添加一个 x 属性,值为‘a‘ [x] = ['a']; // 参数的定义: function userId({id}) { return id; } function whois({displayName: displayName, fullName: {firstName: name}}){ console.log(displayName + "is" + name); } var user = { id: 42, displayName: "jdoe", fullName: { firstName: "John", lastName: "Doe" } }; console.log("userId:" + userId(user)); // "userId: 42" whois(user); // "jdoe is John" function f([x]) { ··· } f(['a']);
也可以在 for-of 循环中使用:
const arr = ['a', 'b']; for (const [index, element] of arr.entries()) { console.log(index, element); } // Output: // 0 a // 1 b
解构赋值的模型 patterns
在解构中,有下面两部分参与:Destructuring source: 解构的源,将要被解构的数据,比如解构赋值表达式的右边部分。
Destructuring target: 解构的目标,比如解构复制表达式的左边部分。
解构的目标可以是下面三个的任意一个:
赋值对象,Assigment Patterns。例如 x . 赋值对象通常来说是一个变量。但是在解构赋值中,你有更多的选择,稍后会讲到。
对象模型,Object Patterns。比如:{first: «pattern», last: «pattern»}
数组模型,Object Patterns。比如:[«pattern», «pattern»]
可以任意嵌套模型,而且是可以非常任性的嵌套。
const obj = { a: [{ foo: 123, bar: 'abc' }, {}], b: true }; const { a: [{foo: f}] } = obj; // f = 123
解构的 patterns 如何访问到值的内部结构?
在一个表达式 pattern = someValue 中,pattern是如何访问 someValue 的呢?Object patterns 将 value 转换成 Object
在访问属性之前,object pattern 将解构的源数据(destructuing source)转换成对象。
const {length : len} = ‘abc’; // len = 3 const {toString: s} = 123; // s = Number.prototype.toString
使用“对象解构”的缺点
在这个过程中,强制转换成对象的过程不是通过 Object() 方法,而是通过内置的操作方法 toObject()。这两个操作处理undefined 和null的方式不太一样。Object()方法将原始类型值转换成包装类型对象(wrapper object),原来的值原封不动。
> typeof Object('abc') 'object' > var obj = {}; > Object(obj) === obj true
也会将undefined 和 null 转换成一个空的对象。
> Object(undefined) {} > Object(null) {}
对比之下,当遇到 undefined 和null的时候,toObject()方法则会抛出一个错误。所以下面的解构是失败的:
const { prop: x } = undefined; // TypeError const { prop: y } = null; // TypeError
因此,你可以使用空对象模型 {} 来检查一个值是否被强制转换成了一个对象。正如前面提到的规则,undefined和 null 将会抛出错误
({} = [true, false]); // OK, Arrays are coercible to objects ({} = 'abc'); // OK, strings are coercible to objects ({} = undefined); // TypeError ({} = null); // TypeError
表达式两边的括号是必须的,因为在 JavaScript 中,声明不能以花括号开始。
在可遍历的量中使用数组模型
数组解构使用一个迭代器来获取数据源中的元素。因此,你可以对任何可以遍历的值使用数组解构。字符串是可遍历的:
const [x, ...y] = 'abc'; // x='a'; y=['b', 'c']
我们无法通过索引访问 Set 中的元素,但是可以通过迭代器。所以,数组解构能够在 Sets 上工作:
const [x,y] = new Set(['a', 'b']); // x='a'; y='b’;
Set的迭代器总是按照元素插入的顺序将元素返回,所以上述的解构返回的结果总是相同的。
使用“数组解构”的缺点
如果一个值有一个 key 为 Symbol.iterator 的方法,这个方法返回的是一个对象,那么这个值是可以遍历的。如果被解构的值不能遍历的,那么“数组解构”会抛出一个 TypeError 错误。let x; [x] = [true, false]; // OK, Arrays are iterable [x] = 'abc'; // OK, strings are iterable [x] = { * [Symbol.iterator]() { yield 1 } }; // OK, iterable [x] = {}; // TypeError, empty objects are not iterable [x] = undefined; // TypeError, not iterable [x] = null; // TypeError, not iterable
可以用一个空的数组模型 [] 来检查值是不是可遍历的:
[] = {}; // TypeError, empty objects are not iterable [] = undefined; // TypeError, not iterable [] = null; // TypeError, not iterable
默认值
默认值是可选的,在数据源中找不到对应的值时,如果设置了默认值,则匹配这个默认值作为匹配结果,否则返回 undefined。const [x=3, y] = []; // x = 3; y = undefined。 const {foo: x=3, bar: y} = {}; // x = 3; y = undefined
undefined 也会触发默认值
当解构模式有匹配结果,且匹配结果是 undefined 时,也会使用默认值作为返回结果:const [x=1] = [undefined]; // x = 1 const {prop: y=2} = {prop: undefined}; // y = 2
默认值是根据需要计算出来的
也就是说下面的解构:
const {prop: y=someFunc()} = someValue;
相当于:
let y; if (someValue.prop === undefined) { y = someFunc(); } else { y = someValue.prop; }
使用 console.log() 可以观察到:
> function log(x) { console.log(x); return 'YES' } > const [a=log('hello')] = []; > a 'YES' > const [b=log('hello')] = [123]; > b 123
在第二个解构中,默认值没有触发,并且 log() 没有被调用。
默认值可以引用模式中的其他变量
默认值可以引用模式中的任何变量,包括相同模式中的其他变量:const [x=3, y=x] = []; // x=3; y=3 const [x=3, y=x] = [7]; // x=7; y=7 const [x=3, y=x] = [7, 2]; // x=7; y=2
但是,变量的顺序很关键,从左到右,先声明的变量不能引用后声明的变量,也就是左边的不能引用右边的。
const [x=y, y=3] = []; // ReferenceError
patterns 的默认值
到目前为止,我们所看到的都是模式中变量的默认值,我们也可以为模式设置默认值。
const [{prop: x} = {}] = [];
如果整个模式没有匹配结果,则使用 {} 作为数据源来匹配。
const { prop: x } = {}; // x = undefined
上面的例子中,x 为 undefined 可能还是不够直观。看下面这个例子:
const [{prop: x} = {props: 'abc'}] = []; // x=abc
对象解构的更多特性
属性,属性值的简写
如果属性值是一个变量,和属性的 key 相同,就可以忽略这个 key:const { x, y } = { x: 11, y: 8 }; // x = 11; y = 8 // 等价于 const { x: x, y: y } = { x: 11, y: 8 };
计算后的属性的键
如果把表达式放入方括号中,可以用这个表达式声明属性的键:const FOO = 'foo'; const { [FOO]: f} = {fooL 123}; // f = 123 这也使得可以使用 symbols 来做属性的键: // Create and destructure a property whose key is a symbol const KEY = Symbol(); const obj = { [KEY]: 'abc' }; const { [KEY]: x } = obj; // x = 'abc' // Extract Array.prototype[Symbol.iterator] const { [Symbol.iterator]: func } = []; console.log(typeof func); // function
数组解构的更多特性
在解构的过程中可以跳过一些元素:const [,,x,y] = [1,2,3,4]; // x= 3 y = 4;
剩余运算符 Rest operator (…)
剩余运算符可以将一个可遍历对象中剩余的元素提取到一个数组中。如果这个运算符在数组模式中使用,运算符必须放在最后:const [x, ...y] = [1,2,3,4]; // x=1; y=[2,3,4];
要注意的时,拓展运算符(spread operator)与剩余操作符有着相同的语法 - 三个点。但是它们之间有区别:前者将数组变成多个元素;后者则用来解构和提取数据,多个元素压缩成一个元素。
如果运算符找不到任何元素,将会匹配一个空的数组,永远不会返回 undefined 或者 null。例如:
const [x, y, ...z] = ['a']; // x='a'; y=undefined; z
操作符不一定非要是一个变量,也可以使用模式:
const [x, ...[y, z]] = ['a', 'b', 'c']; // x = 'a'; y = 'b'; z = 'c'
解构的陷阱
在使用解构的时候,有两点要考虑清楚:不能使用大括号作为声明语句的开头;
在解构的过程中,可以申明变量或者分配给变量,但是不能同时这么做;
解构的几个例子
在 for-of 中使用解构:const map = new Map().set(false, 'no').set(true, 'yes'); for (const [key, value] of map) { console.log(key + 'is' + value); }
使用解构交换两个变量的值:
[a, b] = [b, a];
或者:
[a, b, c] = [c, a, b];
还可以分割数据:
const [first, ...rest] = ['a', 'b', 'c']; // first = 'a'; rest = ['b', 'c']
处理方法返回的数组更加方便:
const [all, year, month, day] = /^(\d\d\d\d)-(\d\d)-(\d\d)$/.exec('2999-12-31'); const cells = 'Jane\tDoe\tCTO' const [firstName, lastName, title] = cells.split('\t'); console.log(firstName, lastName, title);
要注意的一点是:exec 等一些方法可能会返回 null,导致程序抛出错误TypeError,此时需要添加一个默认值:
const [, year, month, day] = /^(\d\d\d\d)-(\d\d)-(\d\d)$/.exec(someStr) || [];
相关文章推荐
- es6中的解构赋值、扩展运算符和rest参数使用详解
- ES6解构赋值实例详解
- 基于ES6作用域和解构赋值详解
- es6解构赋值(二)
- ES6第二章 关于“变量的解构赋值”不得不说的事~
- ECMAScript6(ES6)标准之解构赋值语法及应用
- ES6语法---解构赋值
- ES6---解构赋值number、bool、string、array、function、object
- JavaScript解构赋值(代码说明ES6数组, 对象, 函数的解构赋值)
- js-es6-变量的解构赋值
- ES6 随记(2)-- 解构赋值
- ES6:变量的解构赋值
- ES6 destructuring 解构赋值
- ECMAScript6(ES6)之解构赋值(数组,对象,字符串)
- 【ES6系列】解构赋值
- ES6解构赋值
- 变量的解构赋值 ES6
- ES6-解构赋值
- es6分享——变量的解构赋值
- ES6数组的解构赋值和Set