ES6:變量解構賦值

解構賦值是對賦值運算符的擴展,是一種針對數組或者對象進行模式匹配,然後對其中的變量進行賦值。

數組的解構賦值(Array)

基本

let [a,b,c] = [1,2,3];
console.log('a = '+a);  // 1
console.log('b = '+b);  // 2
console.log('c = '+c);  // 3

可嵌套

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

可忽略

let [,,third] = ["foo","bar","baz"];
console.log('third = '+third);  // baz

let [x, , y] = [1, 2, 3];
console.log('x = '+x);  // 1
console.log('y = '+y);  // 3

剩餘運算符

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

let [x,y,...z] = ['a'];
console.log(x);  // 'a'
console.log(y);  // undefined
console.log(z);  // []
// 如解構不成功,變量的值爲undefined

如解構不成功,變量的值爲undefined

let [ff] = [];
console.log(ff);    // undefined
let [m,n] = [];
console.log(m,n);   // undefined undefined

let bb = [];
bb = [99,88];
let [mm,nn] = bb;
console.log(mm,nn);   // 99 88

不完全解構

等號左邊的模式只匹配一部分的等號右邊的數組,解構依然成功。

let [aaa = 1, bbb] = []; 
console.log(aaa,bbb);   // 1 undefined

let [aaaa, bbbb] = [1,2,3]; 
console.log(aaaa,bbbb);   // 1 2

let [xx, [yy] ,zz] = [1,[2,3],4]; 
console.log(xx,yy,zz);   // 1 2 4

如果等號的右邊不是可遍歷的結構,那麼將會報錯。

// let [k] = 1;  // Uncaught TypeError: 1 is not iterable
// let [x] = false;  
// let [h] = NaN;  
// let [h] = undefined;  
// let [h] = null;  
// let [h] = { };  

上邊代碼中的語句都會報錯,因爲等號右邊的值或是轉爲對象後不具備Iterator接口(前5個表達式),或是本身不具備Iterator接口(最後表達式)。

只要某種數據結構具有Iterator接口,都可以採用數組形式的解構賦值。

let [e,f,g] = new Set(['a','b','c']);
console.log(e);  // a

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();
console.log(sixth);  // 5

解構默認值

let [ee = true] = [];
console.log('ee = '+ee);   // ee = true

let [xxx,yyy='b'] = ['a'];
console.log('xxx = '+xxx); // xxx = a
console.log('yyy = '+yyy); // yyy = b

let [x1,y1='b'] = ['a',undefined];
console.log('x1 = '+x1);  // x1 = a
console.log('y1 = '+y1);  // y1 = b

ES6內部使用嚴格相等運算符(===)判斷一個位置是否有值。所以,如一個數組成員不嚴格等於undefined,默認值是不生效的。

let [x2 = 1] = [undefined];
console.log('x2 = '+x2);  // x2 = 1

let [x3 = 1] = [null];
console.log('x3 = '+x3);  // x3 = null

如默認值是一個表達式,那該表達式是惰性求值的,即只有用到時纔會求值。

function f1(){
    console.log('kkkxxxhhh');
}
let [x4 = f1()] = [1];
console.log('x4 = '+x4); // x4 = 1

// 等價於下面代碼
// let x4;
// if([1][0] === undefined){
//     x4 = f1();
// }else{
//     x4 = [1][0];
// }

默認值可引用解構賦值的其他變量,但該變量須已聲明。

let [m1=1,n1=m1] = [];
console.log('m1 = '+m1+',n1 = '+n1);
// m1 = 1,n1 = 1

let [m2=1,n2=m2] = [2];
console.log('m2 = '+m2+',n2 = '+n2);
// m2 = 2,n2 = 2

let [m3=1,n3=m3] = [1,2];
console.log('m3 = '+m3+',n3 = '+n3);
// m3 = 1,n3 = 2

// let [m4=n4,n4=1] = [];
// console.log('m4 = '+m4+',n4 = '+n4);
// Uncaught ReferenceError: Cannot access 'n4' before initialization

對象模型的解構(Object)

對象解構與數組解構不同:

數組元素是按次序排列的,變量取值是由它的位置決定的;

而對象屬性沒有次序,變量必須與屬性同名才能取到正確的值。

基本

let { k1, h1 } = { k1: 'aaa', h1: 'bbb' };
console.log('k1 = '+k1+',h1 = '+h1);
// k1 = aaa,h1 = bbb
 
let { baz : k } = { baz : 'ddd' };
console.log('k = '+k);
// k = ddd

let { k2, h2 } = { h2: 'bbb' , k2: 'aaa',};
console.log('k2 = '+k2+',h2 = '+h2);
// k2 = aaa,h2 = bbb

let { max } = { foo:'aaa',bar:'bbb', };
console.log('max = '+max);
// max = undefined
// 變量沒有對應的同名屬性,則取不到值,爲undefined

如變量名與屬性名不一致,必須寫成如下樣子:

var {k3:ba} = {k3:'aaa',h3:'bbb',};
console.log('ba = '+ba);
// ba = aaa

let obj = {first:'hello',last:'world'};
let {first:f0,last:l0,} = obj;
console.log('f0 = '+f0+',l0 = '+l0);
// f0 = hello,l0 = world

對象的解構賦值是下面形式的簡寫:

let {first:first,last:last,} = {first:'hello',last:'world'};

對象解構賦值內部機制是先找到同名屬性,後再賦值給對應的變量。

真正賦值的是後者,而不是前者。

let {k4:bb0,} = {k4:'aaa',h4:'bbb'};
console.log('bb0 = '+bb0); 
// bb0 = aaa
console.log('k4 = '+k4);
// Uncaught ReferenceError: k4 is not defined

可嵌套可忽略

let obj0 = {
    p:[
        'Hello',
        {t:'World'}
    ]
};
let {p:[s,{t}]} = obj0;
console.log('s = '+s+',t = '+t);
// s = Hello,t = World

上面代碼段,p是模式,不是變量,不會被賦值。若要p作爲變量賦值,如下:

let obj0 = {
    p:[
        'Hello',
        {t:'World'}
    ]
};
let {p,p:[s,{t}]} = obj0;
console.log(p);
// ["Hello", {t:'World'}]
console.log('s = '+s+',t = '+t);
// s = Hello,t = World
let node = {
    loc:{
        start:{
            line:1,
            column:5,
        }
    }
};
var { loc, loc:{ start }, loc:{ start:{line}, }, } = node;
console.log(line);
// 1
console.log(loc);
// { start: {line:1,column:5,} }
console.log(start);
// {line: 1, column: 5}
let obj1 = {};
let arr1 = [];
( { foo1:obj1.prop,bar1:arr1[0] } = { foo1:"dengjing",bar1:true, } );
console.log(obj1,arr1);
// {prop: "dengjing"} [true]

解構默認值

var {k5 = 3} = {};
console.log('k5 = '+k5);
// k5 = 3

var {k6,h6 = 5} = { k6 : 1 };
console.log('k6 = '+k6+',h6 = '+h6);
// k6 = 1,h6 = 5

var {k7: h7 = 3} = {};
console.log('h7 = '+h7);
// h7 = 3

var {k7: h7 = 3} = {k7:5};
console.log('h7 = '+h7);
// h7 = 5

var { message:msg = `Something went wrong!` } = {};
console.log('msg = '+msg);
// msg = Something went wrong!

默認值生效的條件是:對象的屬性值嚴格等於undefined。

var {d = 3} = {x:undefined};
console.log('d = '+d);
// d = 3

var {f2 = 3} = {x:null};
console.log('f2 = '+f2);
// f2 = 3

如解構失敗,變量值爲undefined。

let {f3} = {bar:'kkxfh'};
console.log('f3 = '+f3);
// f3 = undefined

如解構模式是嵌套的對象,而且子對象所在的父屬性不存在,那麼將會報錯。

// let {foo: {r}} = {baz :`baz` } ;
// Uncaught TypeError: Cannot destructure property `r` of 'undefined' or 'null'.

下段代碼報錯:syntax error

let zzz0 ;
{zzz0} = {zzz0:1};

上代碼會報錯,因js引擎會將{x}理解成一個代碼塊,從而發生語法錯誤。

只有不將大括號寫在行首,避免js將其解釋爲代碼塊,才能解決bug。

let zzz0 ;
({zzz0} = {zzz0:1});
console.log('zzz0 = '+zzz0);
// zzz0 = 1

對象的解構賦值可很方便地將現有對象的方法賦值到某個變量。

let {log,sin,cos} = Math;

數組本質是特殊的對象,因此可對數組進行對象屬性的解構。

let arr = [1,2,3];
let {0:first0,[arr.length-1]:last0} = arr;
console.log('first0 = '+first0,'last0 = '+last0,);
// first0 = 1 last0 = 3

剩餘運算符

let {a9, b9, ...rest} = {a9: 10, b9: 20, c: 30, d: 40};
console.log('a9 = '+a9,'b9 = '+b9,);
// a9 = 10 b9 = 20
console.log(rest);
// {c: 30, d: 40}

字符串的解構(String)

在數組的解構中,解構的目標若爲可遍歷對象,皆可進行解構賦值。

可遍歷對象即實現 Iterator 接口的數據。

let [a09, b09, c09, d09, e09] = 'hello';
console.log('a09 = '+a09,);
// a09 = h
console.log('b09 = '+b09,);
// b09 = e
console.log('c09 = '+c09,);
// c09 = l
console.log('d09 = '+d09,);
// d09 = l
console.log('e09 = '+e09,);
// e09 = o

類似數組的對象都有一個length屬性,因此可對這個屬性進行解構賦值。

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

數值和布爾值的解構賦值

解構賦值時,如等號右邊是數值和布爾值,則會先轉爲對象。

let {toString:s0} = 123;
console.log( s0 === Number . prototype.toString );
// true
console.log(s0);
// ƒ toString() { [native code] }


let {toString:s1} = true;
console.log( s1 === Boolean.prototype.toString );
// true
console.log(s1);
// ƒ toString() { [native code] }

上面代碼段中,數值和布爾值的包裝對象都有toString屬性,so變量都能取到值。

解構賦值的規則是:只要等號右邊的值不是對象或數組,就先將其轉爲對象。

let { prop:prop01 } = undefined;
// Uncaught TypeError: Cannot destructure property `prop` of 'undefined' or 'null'.
let { prop:prop02 } = null;
// Uncaught TypeError: Cannot destructure property `prop` of 'undefined' or 'null'.

上面代碼段報錯,undefined和null無法轉爲對象,so對其進行解構賦值時都會報錯。

函數參數的解構賦值

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

函數add參數表面上是一個數組,但在傳入參數那刻,數組參數就被解構成變量x和y。對於函數內部的代碼來說,它們能感受到的參數就是x和y。

let arr2 = [ [1,2],[3,4] ].map( ([a,b])=>a+b );
console.log(arr2);
// [3, 7]

函數參數的解構也可以使用默認值。

// 爲變量 x、y 指定默認值
function move({x=0,y=0}={}){
    return [x,y];
}
console.log( move({x:3,y:8}) );
// [3, 8]
console.log( move({x:3,}) );
// [3, 0]
console.log( move({}) );
// [0, 0]
console.log( move() );
// [0, 0]
// 爲函數move1的參數指定默認值
function move1({x,y}={x:0,y:0}){
    return [x,y];
}
console.log( move1({x:3,y:8}) );
// [3, 8]
console.log( move1({x:3,}) );
// [3, undefined]
console.log( move1({}) );
// [undefined, undefined]
console.log( move1() );
// [0, 0]

undefined就會觸發函數參數的默認值

let arr3 = [1 , undefined, 3].map( (x = 'yes' ) => x);
console.log( arr3 );
//  [1, "yes", 3]

用途

交換變量值

let oo=1,pp=2;
[oo,pp] = [pp,oo];
console.log( 'oo = '+oo );
// oo = 2
console.log( 'pp = '+pp );
// pp = 1

從函數返回多個值

函數只能返回一個值,如要返回多個值,只能將它們放在數組或對象裏返回。

// 返回一個數組
function example(){
    return [1,2,3];
}
let [x5,h5,r5] = example();
console.log(x5,h5,r5);
// 1 2 3

// 返回一個對象
function example1(){
    return {
        x8:1,
        h8:2,
        r8:3,
    };
}
let {x8,h8,r8,} = example1();
console.log(x8,h8,r8,);
// 1 2 3

函數參數的定義

// 參數是一組有次序的值
function ffff([x,y,z]){
    // ...
}
ffff([1,2,3])

// 參數是一組無次序的值
function ffff1({x,y,z}){
    // ...
}
ffff1({z:3,y:2,x:1})

提取JSON數據

解構賦值對提取JSON對象中的數據很好用

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

函數參數的默認值

指定參數的默認值,如此就避免了在函數體內部再寫var foo = config.foo||'default.foo';這樣的語句。

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

遍歷Map結構

任何部署了Iterator接口的對象都可以用for...of...循環遍歷。

Map結構原生支持Iterator接口,配合變量的解構賦值獲取鍵名和鍵值就非常方便了。

var map = new Map();
map.set('fst','hello');
map.set('snd','world');
for(let [key,value] of map){
    console.log(key+' is '+value);
}
// fst is hello
// snd is world

如只想獲取鍵名或鍵值,可寫成如下樣:

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

輸入模塊的指定方法

加載模塊時,往往需要指定輸入的方法。解構賦值使得輸入語句非常清晰。

const { SourceMapConsumer, SourceNode } = require ('source-map');
console.log(SourceMapConsumer);
console.log(SourceNode);

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章