解構賦值(Destructuring)
解構賦值
ES6 允許按照一定模式,從陣列和物件中提取值,對變數進行賦值,這被稱為解構(Destructuring)。
陣列的解構賦值
只要等號兩邊模式相同,左邊變數就會被賦上對應的值,使用例子如下:
//以前ES5只能這樣給變數賦值let a = 1;let b = 2;let c = 3;//現在能夠這樣寫,效果等同上面一個個賦值let [a, b, c] = [1, 2, 3];//又如 :let [name, [[says], baz]] = [1, [[2], 3]];console.log(name); // 1console.log(says); // 2console.log(baz); // 3let [ , , third] = ["name", "says", "baz"];console.log(third); // "baz"let [x, , y] = [1, 2, 3];console.log(x); // 1console.log(y); // 3let [head, ...tail] = [1, 2, 3, 4];console.log(head); // 1console.log(tail); // [2, 3, 4]
如果解構不成功,變數的值就等於undefined
let [x, y, ...z] = ['a'];console.log(x); // "a"console.log(y); // undefinedconsole.log(z); // []let [name] = [];//name=undefinedlet [says, name] = [1];//name=undefined
不完全解構的情況(左邊元素少於右邊元素),可以成功:
let [x, y] = [1, 2, 3];console.log(x); // 1console.log(y); // 2let [a, [b], d] = [1, [2, 3], 4];console.log(a); // 1console.log(b); // 2console.log(d); // 4
陣列解構時如果右邊並非陣列,就會報錯
let [name] = 1;//報錯let [name] = false;//報錯let [name] = NaN;//報錯let [name] = undefined;//報錯let [name] = null;//報錯let [name] = {};//報錯
解構賦值允許指定默認值,而ES6內部使用嚴格相等運算符(===),判斷一個位置是否有值。所以,只有當一個 陣列元素等於undefined,默認值才會生效 ,包含null值也不會生效因為null並不嚴格等於undefined。
let [name = true] = [];console.log(name); // truelet [x, y = 'b'] = ['a']; // x='a', y='b'let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'let [x = 1] = [undefined];console.log(x); // 1let [x = 1] = [null];console.log(x); // null//給函數作為默認值function f() {console.log('aaa');}let [x = f()] = [1];//x=1,因為x能解構賦值的到1,f()根本不會被調用
默認值可以引用解構賦值的其他變數,但該變數必須是已定義的
let [x = 1, y = x] = []; // x=1; y=1let [x = 1, y = x] = [2]; // x=2; y=2let [x = 1, y = x] = [1, 2]; // x=1; y=2let [x = y, y = 1] = []; // ReferenceError: y is not defined
物件的解構賦值
解構賦值不只用於陣列,物件亦然,但有一不同點,物件的解構賦值不需要排列順序一致,但需要Key名相同;而陣列不存在Key名,所以左右邊排列順序須一致。
let { name, says } = { name: 'aaa', says: 'bbb' };console.log(name); // "aaa"console.log(says); // "bbb"let { says, name } = { name: 'aaa', says: 'bbb' };console.log(name); // "aaa"console.log(says); // "bbb"let { baz } = { name: 'aaa', says: 'bbb' };console.log(baz); // 右邊並沒有Key名為baz,所以是undefined//物件Math內有log、sin、cos,透過解構賦值就能夠直接獲得值let Math = {log: 10, sin: 0.5 ,cos: 0.333};let { log, sin, cos } = Math;console.log(log);//10console.log(sin);//0.5console.log(cos);//0.333
物件的解構賦值有一點要注意,事實上物件的解構賦值是下面程式碼的簡寫,也就是說物件的解構賦值是先找到同名Key名,然後再賦給對應的變數值, 若無像例3那樣指定後面的變數Key名,則變數的Key名默認與同名Key名相同(如例2)。換句話說,這可以用在解構後想重新命名的情況。
//例1:let { name: name, says: says } = { name: 'aaa', says: 'bbb' };////1等同2//例2:let { name, says } = { name: 'aaa', says: 'bbb' };//(解構賦值簡寫)//例3:let { name: baz } = { name: 'aaa', says: 'bbb' };//console.log(baz) // "aaa"//例4:let obj = { first: 'hello', last: 'world' };let { first: f, last: l } = obj;console.log(f); // 'hello'console.log(l); // 'world'
與陣列一樣,解構賦值也可以用於嵌套結構的物件
let obj = {p: ['Hello',{ y: 'World' }]};let { p: [x, { y }] } = obj;console.log(x); // "Hello"console.log(y); // "World"//值得注意的是上面程式碼,此時p是匹配的Key名,所以不會被賦值,若p也要作為變數賦值,可以寫成下面這樣:let obj = {p: ['Hello',{ y: 'World' }]};let { p, p: [x, { y }] } = obj;console.log(x); // "Hello"console.log(y); // "World"console.log(p); // ["Hello", {y: "World"}]
下面是另一个例子。
const node = {loc: {start: {line: 1,column: 5}}};let { loc, loc: { start }, loc: { start: { line }} } = node;console.log(line) // 1 等同 let { loc, loc: { start }, loc: { start: { line:line }} } = node;(縮寫)console.log(loc) // Object {start: Object} 等同 let { loc:loc, loc: { start }, loc: { start: { line:line }} } = node;console.log(start) // Object {line: 1, column: 5} 等同 let { loc, loc: { start:start }, loc: { start: { line }} } = node;
上面解構賦值中,loc是 loc : loc 縮寫方式,先找相同Key再賦值給同名變數。 下面是"嵌套賦值"的例子,是一非常實用的功能:
let obj = {};let arr = [];({ name: obj.prop, says: arr[0] } = { name: 123, says: true });console.log(obj); // {prop:123}console.log(arr); // [true]//如果解構模式是嵌套的物件而且子物件的父物件不存在,那將會報錯//如下name解構時解構不成功等於undefined,此時不抱錯,再往下取子物件就會報錯,因為相當於取undefined的子物件let {name: {says}} = {baz: 'baz'};//報錯
需要注意一點,解構賦值是可以取得子屬性的:

const obj1 = {};const obj2 = { foo: 'bar' };Object.setPrototypeOf(obj1, obj2);//如上圖打印obj1const { foo } = obj1;console.log(foo); // "bar"//foo 並不是odj1自身的屬性,而是繼承自obj2的屬性,但是解構賦值可以取到此屬性
物件的解構賦值也可以像陣列一樣指定默認值,生效條件一樣也是"屬性值嚴格等於undefined"
let {x = 3} = {};console.log(x); // 3let {x, y = 5} = {x: 1};console.log(x); // 1console.log(y); // 5let {x: y = 3} = {};console.log(y); // 3let {x: y = 3} = {x: 5};console.log(y); // 5let { message: msg = 'Something went wrong' } = {};console.log(msg); // "Something went wrong"//生效條件是"屬性值嚴格等於undefined"let {x = 3} = {x: undefined};console.log(x); // 3let {x = 3} = {x: null};console.log(x); // null ,因為null與undefined不嚴格相等所以不啟用默認值
下面將幾種需要注意的錯誤列出來:
(1)如果要將一個已定義的變數用於解構賦值,不能將 " { " 置於行首,因為javascript引擎會將{x}解析成一代碼塊,從而發生代碼錯誤, 避免javascript解析為代碼塊才能解決此問題,
//錯誤寫法let x;{x} = {x: 1};// SyntaxError: syntax error//正確寫法let x;({x} = {x: 1});//放在一個圓括號裡可以正常執行 但無法寫成 ({x:c} = {x: 1}) 會報錯
(2)解構賦值允許等號左邊不放置任何變數名,因此雖然無意義但語法合法可執行。
({} = [true, false]);({} = 'abc');({} = []);
(3)由於陣列其實是特殊的物件,因此可以對陣列進行物件屬性的解構。這在陣列轉物件時很好用。
let arr = [1, 2, 3];let {0 : first, [arr.length - 1] : last} = arr;//屬性名表達式console.log(first); // 1console.log(last); // 3
字串的解構賦值
字串也可以解構賦值,因為字串此時被轉成了類似陣列的物件。
const [a, b, c, d, e] = 'hello';console.log(a); // "h"console.log(b); // "e"console.log(c); // "l"console.log(d); // "l"console.log(e); // "o"
而類似於陣列的物件都有一個length屬性,因此亦可以對此屬性進行解構賦值。(如 Object.setPrototypeOf 的那個屬性)
let {length : len} = 'hello';//屬性lengthconsole.log(len); // 5
參數的解構賦值
函數的參數也可以使用解構賦值,如下給函數add傳入參數,經過解構賦值後成了變數x與y。
function add([x, y]){return x + y;}add([1, 2]); // 回傳 : 3//使用map()亦然[[1, 2], [3, 4]].map(([a, b]) => a + b);// 得新陣列 : [ 3, 7 ][1, undefined, 3].map((x = 'yes') => x);// 得新陣列 : [ 1, 'yes', 3 ]
當然也可以使用默認值,若傳入值為undefined就會觸發使用函數參數的默認值。
function move({x = 0, y = 0} = {}) {return [x, y];}//注意以下move({x = 0, y = 0} = {})會先檢查參數是否為undefinedmove({x: 3, y: 8}); // 回傳 : [3, 8]move({x: 3}); // 回傳 : [3, 0] , {x = 0, y = 0} = {x: 3} 解構發現 y 為undefined,觸發y的默認值0move({}); // 回傳 : [0, 0] ,{x = 0, y = 0} = {} 解構發現 x , y 為undefined,觸發x、y的默認值0move(); // 回傳 : [0, 0] ,move(物件參數 = {})中物件參數傳入undefined,觸發move函數的默認值{},再解構觸發x、y的默認值0//若將move函數改寫如function move({x = 0, y = 0} = {x: 1}) {return [x, y];}move(); // 回傳 : [1, 0],move(物件參數 = {})中物件參數傳入undefined,觸發move函數的默認值{x: 1},再解構x為1、觸發y的默認值0//下方更進一步改寫,可以更清楚的理解其過程function move({x, y} = { x: 0, y: 0 }) {//是為函數move的參數指定默認值,而不是為變數x和y指定默認值return [x, y];}move({x: 3, y: 8}); // 回傳 : [3, 8]move({x: 3}); // 回傳 : [3, undefined]move({}); // 回傳 : [undefined, undefined]move(); // 回傳 : [0, 0]