解構賦值(Destructuring)
ES6 允許按照一定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構(Destructuring)。
數組的解構賦值
以前,爲變量賦值,只能直接指定值。
let a = 1;
let b = 2;
let c = 3;
ES6 允許寫成下面這樣。
//等號左邊是在數組中聲明多個變量
//等號右邊是一個數組
let [a, b, c] = [1, 2, 3];
console.log(a,b,c);//->1 2 3
上面的代碼表示從,可以從數組中提取值,按照位置賦值給左側的變量
默認值
解構賦值允許指定默認值。
//y在右側數組中對應位置沒有值,即undefined,會使用默認值
let [x,y=2]=[1] //x->1 y->2
//同上
let [x=1,y]=[,2] //x->1 y->2
//同上
let [x=1,y=2]=[] //x->1 y->2
注意,ES6 內部使用嚴格相等運算符(===),判斷一個位置是否有值。所以,只有當一個數組成員嚴格等於undefined
,默認值纔會生效。
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
默認值可以是一個表達式,只不過這個表達式是惰性的,使用到的時候纔會執行求值。
function f(){ console.log("aaaaaa"); return 1; }
let [x=f()]=[];
//aaaaaa
//x->1
默認值可以引用解構賦值的其他變量,但該變量必須已經聲明。
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還沒有聲明。
注意事項
- 如果解構不成功,那麼變量的值將會是
undefined
let [foo] = [];
let [bar, foo] = [1];
以上兩種情況foo
的值都是undefined
- 數組的解構可以用於嵌套賦值
let [x,[y,z],l]=[1,[2,3],4]
//x->1,y->2,z->3,l->4
- 解構存在不完全解構,即等號左邊的模式,只匹配一部分等號右邊的數組。這種情況下依然可以解構成功。
let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
以上兩個例子都屬於不完全解構,但是夠可以成功。
- 如果等號右邊的不是數組,嚴格的說不是可以遍歷的結果,那麼將會報錯。
// 報錯
let [foo] = 1; //VM415:1 Uncaught TypeError: 1 is not iterable
let [foo] = false; //VM415:1 Uncaught TypeError: false is not iterable
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
以上代碼都會報錯。
- 只要某種數據結構具有 Iterator 接口,都可以採用數組形式的解構賦值
let [a,b,c]=new Set([1,2,3]);
//a->1,b->2,c->3
對象的解構賦值
解構賦值不僅可以用於數組,也可以用於對象。
//這裏的變量名與對象的屬性同名
let {foo,bar}={foo:"a",bar:"b"}
//foo->a bar->b
對象的解構與數組有一個重要的不同,數組中的元素是按照次序排列的,變量名的取值由他的位置決定;而對象沒的屬性沒有次序,變量名必須與屬性同名才能取到正確的值。即數組的結果是按照元素的次序給變量賦值,對象的解構是按照變量名和屬性名匹配做賦值。
如果要賦值的變量名和對象屬性名不一樣,可以使用匹配模式:變量名稱
寫法。
//f、b爲匹配模式,會去匹配同名的屬性
//將匹配到的同名屬性的值賦給foo、baz
let {f:foo,b:baz}={f:"a",b:"b"}
//foo->a bar->b
其實第一個示例代碼的完整寫法是這樣的。
//這裏的變量名與對象的屬性同名
let {foo:foo,bar:bar}={foo:"a",bar:"b"}
//foo->a bar->b
默認值
對象的解構賦值也可以給默認值
let {foo='a',bar}={bar:'b'} //foo->a bar->b
let {foo='a',bar='b'}={} //foo->a bar->b
let {foo='a',bar=foo}={} //foo->a bar->a
var { message: msg = 'Something went wrong' } = {};
//msg->"Something went wrong"
使用默認值的條件是,對象屬性值嚴格等於(===)undefined
let {foo='a'}={foo:undefined} //foo->a
let {foo='a'}={foo:null} //foo->null
注意事項
- 對象解構失敗,默認值將會是
undefined
,這一點和數組解構賦值一致
let {foo,bar}={foo:"a"}
//foo->a bar->undefined
- 對象的解構也可以用於嵌套賦值,這一點和數組的解構嵌套賦值一致
let {foo,bar:{baz}}={foo:"a",bar:{baz:"b"}}
//foo->a,bar->b
下面是一個更爲複雜的例子
const node = {
loc: {
start: {
line: 1,
column: 5
}
}
};
let { loc, loc: { start }, loc: { start: { line }} } = node;
//loc->{start: {…}}
//start->{line: 1, column: 5}
//line->1
上面的代碼進行了三個解構賦值,分別是loc、start和line。這裏需要注意的是:loc解構賦值時,loc即時匹配模式又是變量;start解構賦值時,loc是匹配模式,start即使匹配模式又是變量;line解構賦值時,loc\start都是匹配模式,line即是匹配模式又是變量。
- 如果解構模式是嵌套的對象,而且子對象所在的父屬性不存在,那麼將會報錯。
// 報錯
let {foo: {bar}} = {baz: 'baz'};
foo模式需要匹配一個對象,但是foo並沒有匹配到一個對象,值將會是undefined
,undefined.bar
會報錯。
幾個用於鞏固知識點的代碼示例
- 解構一個對象,賦值給數組
function test(){
let array=[];
let obj={foo:'a',bar:'b'};
({foo:array[0],bar:array[1]}=obj);
console.log("array:",array);//["a", "b"]
}
test();
- 解構一個數組,賦值給對象
function test1(){
var obj={foo:[],bar:[]};
var array = [[1,2],['a','b']];
([obj.foo,obj.bar]=array);
console.log("obj:",obj);//{bar:["a", "b"],foo:[1, 2]}
}
test1()
- 對象和數組解構賦值嵌套使用
function test2(){
var obj={userName:[{fistName:'Zhang'},{lastName:'Xueyuan'}]}
var userNameObj={};
var userNameAray=[];
({userName:[{fistName:userNameObj.fistName},{lastName:userNameObj.lastName}]}=obj);
({userName:[{fistName:userNameAray[0]},{lastName:userNameAray[1]}]}=obj);
console.log("userNameObj:",userNameObj);
//{fistName: "Zhang", lastName: "Xueyuan"}
console.log("userNameAray:",userNameAray);
//["Zhang", "Xueyuan"]
}
test2()
字符串的解構賦值
字符串也可以解構賦值,如果等號的右側是字符串,那麼該字符串會轉換成數組對象(Array)
let [a,b]="ab"; //a->a b->b
//上面的代碼相當於
let [a,b]=new Array('a','b');
字符串轉換數組對象後,也可以使用對象的解構方式解構數組對象的屬性
let {length:len}="ab"; //len->2
也就是說字符串的解構賦值,既可以使用對象的解構賦值,也可以使用數組的解構賦值。
數值和布爾值的解構賦值
數值和布爾值也可以解構賦值,如果等號的右側是數值或布爾值,那麼數值或布爾值將轉換正對應的包裝類,數值Number,布爾值Boolean。
let {valueOf:fun}=true;
let {valueOf:fun}=123;
//這裏是把valueOf函數賦值給了變量,並不是將valueOf函數的執行結果賦值給fun
注意:數值和布爾值不能使用數組解構賦值,應該兩者都不是可迭代(Iteration)的對象。
函數的參數解構賦值
解構賦值可以用於數組和對象,也可以用於函數的解構賦值。原理都是一樣的,一個函數需要一個或多個順序排列的形參,在調用是順序傳入實參,使用解構數組或對象的形式作爲形參,調用的時候傳入數組或對象作爲實參。
//普通的寫法
function add(x,y){
return x+y;
}
add(1,2);//->3
//使用數組解構賦值的形式給函數傳遞參數
function add([x,y]){
return x+y;
}
add([1,2]);//->3
//使用對象解構賦值的形式給函數傳遞參數
function add({x,y}){
return x+y;
}
add({x:1,y:2});//->3
默認值
函數參數的解構賦值也可以使用默認值。
//函數參數數組解構賦值指定默認值
function add([x=0,y=0]=[]){
return x+y;
}
console.log(add([1,2]));//->3 相當於[x=0,y=0]=[1,2]
console.log(add([1]));//->1 相當於[x=0,y=0]=[1]
console.log(add([]));//->0 相當於[x=0,y=0]=[]
console.log(add());//->0 //使用默認值[],相當於[x=0,y=0]=[]
//函數參數對象解構賦值指定默認值
function add({x=0,y=0}={}){
return x+y;
}
console.log(add({x:1,y:2}));//->3 相當於{x=0,y=0}={x:1,y:2}
console.log(add({x:1}));//->1 相當於{x=0,y=0}={x:1}
console.log(add({}));//->0 相當於{x=0,y=0}={}
console.log(add());//->0 //使用默認值{},相當於{x=0,y=0}={}
注意:[x=0,y=0]=[]
和{x=0,y=0}={}
等號左邊是解構賦值的變量,右側是調用函數不傳入任何參數時使用的默認值,而不是指定x、y變量的默認值。add()
調用形式會使用默認值。
默認值非{}、[]的例子
function add([x=0,y=0]=[1,2]){
return x+y;
}
console.log(add([1,2]));//->3
console.log(add([1]));//->1
console.log(add([]));//->0
console.log(add());//->3 //使用默認值{},相當於[x=0,y=0]=[1,2]
用途
- 交替變量的值
- 從函數返回多個值
- 函數參數的定義
- 提取JSON
- 函數參數的默認值
- 便利Map接口
- 輸入模塊的指定方法
function forMap(){
let map=new Map();
map.set("name","zhangxy");
map.set("age",30);
for(let [key,value] of map){
console.log(key,":",value);
}
}
forMap();
//單獨獲取key
for(let [key] of map){
console.log(key,":",value);
}
//單獨獲取value
for(let [,value] of map){
console.log(key,":",value);
}
總結起來解構賦值的語法讓變量的賦值更簡潔,用更少的代碼就能從數組或對象中獲取值賦給變量
學習資料:變量的解構賦值(https://es6.ruanyifeng.com/#docs/destructuring)