ES6非常棒的特性-解构(Destructuring)
https://blog.csdn.net/maoxunxing/article/details/79772946
总结:
1,被解构的数据结构必须有Iterator接口,换句话说,只要有Iterator接口的数据结构就能被解构
举例:数组,对象,Generator函数(如下)
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 // 5 斐波那契数列
2,数组解构赋值:
2.1 数组的解构赋值属于模式匹配:只要等号两边的模式相同,左边的变量就会被赋予对应的值
举例:
// 简单匹配 let [a, b, c] = [1, 2, 3]; // 嵌套数组匹配 let [foo, [[bar], baz]] = [1, [[2], 3]]; foo // 1 bar // 2 baz // 3 // 空位匹配 let [ , , third] = ["foo", "bar", "baz"]; third // "baz" let [x, , y] = [1, 2, 3]; x // 1 y // 3 // 扩展运算符匹配 let [head, ...tail] = [1, 2, 3, 4]; head // 1 tail // [2, 3, 4] y // 3
2.2 解构不成功与不完全解构
2.2.1 解构不成功
等号左边多于等号右边会出现解构不成功的现象,等号左边未被赋值的变量值为undefined
2.2.2 不完全解构
相对应的等号左边少于等号右边会出现不完全解构的现象,解构可以成功
举例:
// 解构不成功 let [foo] = []; foo //undefined let [bar, foo] = [1]; foo //undefined let [x, y, ...z] = ['a']; x // "a" y // undefined z // [] // 不完全解构 let [x, y] = [1, 2, 3]; x // 1 y // 2 let [a, [b], d] = [1, [2, 3], 4]; a // 1 b // 2 d // 4
2.3 数组解构中的默认值
2.3.1 使用默认值可以避免解构不成功的尴尬
举例:
// 当等号右边相应位置不存在或者全等于'==='undefined时,如果等号左边存在默认值,那么默认值生效 let [foo = true] = []; foo // true 生效 let [x, y = 'b'] = ['a']; // x='a', y='b' 生效 let [x, y = 'b'] = ['a', undefined]; // x='a', y='b' 生效 let [x = 1] = [undefined]; x // 1 生效 let [x = 1] = [null]; x // null 不生效
2.3.2 解构赋值中的默认值是表达式时,表达式是惰性求值的
举例:
// 此时函数不会执行 function f() { console.log('aaa'); } let [x = f()] = [1]; x //1 不会打印'aaa' //等价于 let x; if ([1][0] === undefined) { x = f(); } else { x = [1][0]; }
2.3.3 默认值可以为变量,但是必须已经被声明
举例:
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 is not defined // 最后一个表达式之所以会报错,是因为x用y做默认值时,y还没有声明
3,对象的解构赋值
3.1 对象解构赋值机制:先找到同名属性,然后再赋给对应的变量。真正被赋值的是变量,而不是属性。
举例:
let { foo: baz } = { foo: 'aaa', bar: 'bbb' }; baz // "aaa" 代码中,foo
是匹配的模式,baz
才是变量。真正被赋值的是变量baz
,而不是模式foo
。 let obj = { first: 'hello', last: 'world' }; let { first: f, last: l } = obj; f // 'hello' l // 'world'
3.2对象赋值的简写形式:由于es6中规定了对象的简写形式,即ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。
举例:
let birth = '2000/01/01'; const Person = { name: '张三', //等同于birth: birth birth, // 等同于hello: function ()... hello() { console.log('我的名字是', this.name); } };
所以相应的解构赋值可以缩写为:
举例:
let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; foo // "aaa" bar // "bbb" //需要注意:对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。 let { bar, foo } = { foo: 'aaa', bar: 'bbb' }; foo // "aaa" bar // "bbb"
// 应用:对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。 // 例一 let { log, sin, cos } = Math; // 例二 const { log } = console; log('hello') // hello // 上面代码的例一将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多。例二将console.log赋值到log变量。
3.3 对象解构失败:与数组类似,如果等号左边的属性名在右边没有相对应的属性名,那么该属性名所对应的属性值变量解构失败,最后等于undefined
举例:
let {foo} = {bar: 'baz'}; foo // undefined
3.4 嵌套解构对象的解构:注意两个概念:模式与变量,模式可以理解为等号左边的属性名,变量可以理解为等号左边的属性值
3.4.1 对象的嵌套解构可以类比于非简写形式的普通解构,因为嵌套解构对象时,属性名和属性值不可能相同,所以不存在简写形式
属性值的解构与普通的解构方法相同,举例
let obj = { p: [ 'Hello', { y: 'World' } ] }; let { p: [x, { y }] } = obj; x // "Hello" 数组解构 y // "World" 对象解构简写
3.4.2 查看上述代码发现,解构中的p为模式,那么就没有能获取p属性值的变量,若想获取属性p值,可以通过下述方法获得,类似与双重解构
举例:
let obj = { p: [ 'Hello', { y: 'World' } ] }; let { p, p: [x, { y }] } = obj; x // "Hello" y // "World" p // ["Hello", {y: "World"}] p首先进行了普通解构,然后进行了嵌套解构
举例二:
const node = { loc: { start: { line: 1, column: 5 } } }; let { loc, loc: { start }, loc: { start: { line }} } = node; line // 1 loc // Object {start: Object} start // Object {line: 1, column: 5}
//上面代码有三次解构赋值,分别是对loc
、start
、line
三个属性的解构赋值。注意,最后一次对line
属性的解构赋值之中,只有line
是变量,loc
和start
都是模式,不是变量。
3.4.3 嵌套赋值:使用分组运算符()进行赋值
举例:
let obj = {}; let arr = []; ({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true }); obj // {prop:123} arr // [true]
解构赋值中小括号的应用:https://www.cnblogs.com/wangtong111/p/12165120.html
3.5 嵌套对象解构的报错:如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。
举例:
// 报错 let {foo: {bar}} = {baz: 'baz'}; // 上面代码中,等号左边对象的foo属性,对应一个子对象。该子对象的bar属性,解构时会报错。原因很简单,因为foo这时等于undefined,再取子属性就会报错。
3.6 对象的解构赋值可以取到继承的属性
注:[[prototype]]
和__proto__
意义相同,均表示对象的内部属性,其值指向对象原型。前者在一些书籍、规范中表示一个对象的原型属性,后者则是在浏览器实现中指向对象原型。
const obj1 = {}; const obj2 = { foo: 'bar' }; Object.setPrototypeOf(obj1, obj2); const { foo } = obj1; foo // "bar" // 对象obj1的原型对象是obj2。foo属性不是obj1自身的属性,而是继承自obj2的属性,解构赋值可以取到这个属性。 // Object.setPrototypeOf(obj, prototype) // obj:要设置其原型的对象。 // prototype:该对象的新原型(一个对象 或 null)
3.7 对象解构的默认值:
3.7.1 一般用法:与数组的解构类似,且要注意对象的省略写法
举例:
var {x = 3} = {}; x // 3 var {x, y = 5} = {x: 1}; x // 1 y // 5 var {x: y = 3} = {}; y // 3 var {x: y = 3} = {x: 5}; y // 5 var { message: msg = 'Something went wrong' } = {}; msg // "Something went wrong"
3.7.2 默认值有效的前提依然是:对象的属性值严格等于undefined
。
var {x = 3} = {x: undefined}; x // 3 var {x = 3} = {x: null}; x // null // 上面代码中,属性x等于null,因为null与undefined不严格相等,所以是个有效的赋值,导致默认值3不会生效。
注意:1⃣️一般来说解构赋值等号左边为变量声明,如果是已经声明的变量,进行解构赋值时左边会出现大括号在开头的情况,此时由于javascript引擎会将大括号解析为代码块,所以会报错,正确示例:
// 正确的写法 let x; ({x} = {x: 1});
2⃣️由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构,举例:
let arr = [1, 2, 3]; let {0 : first, [arr.length - 1] : last} = arr; first // 1 last // 3 // 上面代码对数组进行对象解构。数组arr的0键对应的值是1,[arr.length - 1]就是2键,对应的值是3。方括号这种写法,属于“属性名表达式”
4,字符串的解构赋值
4.1 非常简单,相当于转换为单字符的数组进行解构赋值,举例:
const [a, b, c, d, e] = 'hello'; a // "h" b // "e" c // "l" d // "l" e // "o"
4.2 类数组对象都有一个length属性,可以单独解构赋值,据我理解,只要存在的属性都可以被解构赋值,举例:
let {length : len} = 'hello'; len // 5
5,数值和布尔值的解构赋值
5.1 解构赋值时,如果等号右边是数值和布尔值,则会先转为对象,严格意义上来说,可以看作将单纯的数值与布尔值转换为了内置Number和Boolean构造函数的实例对象,举例:
let {toString: s} = 123; s === Number.prototype.toString // true let {toString: s} = true; s === Boolean.prototype.toString // true // 释义:对象化之后s获取了相应原型的toString属性,所以两者相等,此时s的值为 function toString() { [native code] } 即原型的toString函数
5.2 undefined
和null
无法转为对象,所以对它们进行解构赋值,都会报错,举例:
let { prop: x } = undefined; // TypeError let { prop: y } = null; // TypeError
6,函数参数的解构赋值:
函数参数的解构意思是定义函数时可以设定一个函数参数参数为数组或者对象,调用函数传入参数时,将形参放在等号左边,实参放到等号右边进行解构,解构之后的变量可以用在函数中。
6.1 简单使用,举例:数组+对象
function add([x, y]){ return x + y; } add([1, 2]); // 3 function move({x = 0, y = 0}) { return [x, y]; } move({x: 3, y: 8}); // [3, 8]
6.2 使用函数的参数解构赋值时,若函数形参没有默认值,则实参必须传入,否则会报错,因为解构时等号的右边为空,举例:注1
function add([x, y]){ console.log(x + y); } add(); // Error: undefined is not iterable (cannot read property Symbol(Symbol.iterator)) function move({x = 0, y = 0}) { return [x, y]; } move(); // Error: Cannot destructure property `x` of 'undefined' or 'null'.
6.3 默认值有两种情况,一种是参数解构的默认值,另一种是函数参数的默认值,表现是不同的
6.3.1 参数解构默认值:函数move
的参数是一个对象,通过对这个对象进行解构,得到变量x
和y
的值。如果解构失败,x
和y
等于默认值。
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]
6.3.2 函数参数默认值:为函数move
的参数指定默认值,而不是为变量x
和y
指定默认值,所以会得到与前一种写法不同的结果。
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]
6.4 参数解构与普通解构相同,undefined会触发函数参数的默认值,举例:
[1, undefined, 3].map((x = 'yes') => x); // [ 1, 'yes', 3 ]
7,圆括号问题
todo
8,用途
todo
注1:报错含义及报错不同的原因待考究。