ES6標準入門之變數的解構賦值

你的聲先生發表於2019-05-08

在正式的講解之前,我們先來分析一下,到底什麼是解構賦值? 通過對片語的分析,我們大致能理解,解構---也就是破壞,破壞原來的結構,賦值---當然就是再次賦值。下面我們來具體的分析一下,到底什麼是解構賦值。

陣列的解構賦值

1.基本用法

ES6允許按照一定模式從陣列和物件中提取值,然後對變數進行賦值,這被稱作解構(Destructuring)。 以前,為變數賦值只能直接指定值。

let a = 1;
let b = 2;
let c = 3;
複製程式碼

ES6允許寫成這樣。

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,  , z] = [1,2,3];
 x //1
 z //3
 
 let [head, ...tail] = [1,2,3,4];
 head // 1 
 tail // [2,3,4]
 
 let [x, y, ...z] = ["a"];
 x //"a"
 y // undefined
 z //[]  
複製程式碼

如果解構不成功,變數的值就等於undefined。

let [foo] = [];
let [bar, foo] = [1];
複製程式碼

上面的foo取值都是會等於undefined. 另一種情況是不完全解構,即等號左邊的模式只匹配一部分等號右邊的陣列,這種情況,解構依然可以成功。

let [x, y] = [1,2,3];
 x //1;
 y //2;
 
 let [a, [b], c] = [1, [1,3], 4];
 a //1
 b //1
 c //4
複製程式碼

上面的例子屬於不完全解構,但是可以成功。 如果等號的右邊不是陣列,嚴格來說是不可遍歷的結構,那麼將會報錯。

let [foo] = 1;
let [foo] = false;
let [foo] = undefined;
let [foo] = NaN;
let [foo] = null;
let [foo] = {};
複製程式碼

上面的語句會報錯,因為等號右邊的值或者是轉換為物件以後不具備Iterator介面,或者本身就不具備Iterator介面。(最後一個表示式) 簡單的介紹一下Iterator介面。 遍歷器(Iterator)是一種介面,為各種不同的資料結構提供統一的訪問機制,任何資料結構,只要部署Iterator介面,就可以完成遍歷操作。 以下資料結構具備原生的Iterator介面。(可使用for...of方法進行迴圈遍歷)

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函式的arguments物件
  • NodeList物件

2.Set與Map結構的解構賦值

let [x,y,z] = new Set(["a","b","c"]);
x// "a"
var map = new Map();
map.set("first","hello");
map.set("second","world");
for(let [key, value] of map){
    console.log(key +" is " + value);
}
複製程式碼

3.預設值

解構賦值允許指定預設值。

let [foo = true] = [];
foo //true

let [x,y = 'b'] = ['a'];
x //'a';
y //'b';

let [x, y = 'b'] = ['a', undefined];
x // 'a'
y // 'b'
複製程式碼

ES6內部使用嚴格相等運算子(===)判斷一個位置是否有值。所以,如果一個陣列成員不嚴格等於undefined,預設值不會生效。

let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x //null
複製程式碼

上面的第二個例子沒有取到預設值,是因為null不嚴格等於undefined。 而且,需要注意的是,預設值可以引用解構賦值的其他變數,但該變數必須已經宣告。

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
複製程式碼

最後一個報錯,是因為,x用到預設值y時,y還沒有宣告。

物件的解構賦值

解構賦值不僅可以用於陣列,還可以用於物件。

let {foo, bar} = {foo:"aaa", bar: "bbb"};
foo // "aaa"
bar // "bbb"
複製程式碼

與陣列的區別

物件的解構賦值與陣列有一個重要的不同。陣列的元素是按次序排列的,變數的取值是由它的位置決定的,而物件的屬性沒有次序,變數必須與屬性同名才能取到正確的值

let {foo, bar} = {bar: "bbb", foo:"aaa"};
foo // "aaa"
bar // "bbb"
let {baz} = {bar: "bbb", foo:"aaa"};
baz //undefined
複製程式碼

上面的程式碼就驗證了,取值與次序無關。 第二個例子沒有取到值,是因為變數沒有對應的同名屬性,導致取不到值,最後等於undefined。 如果變數與屬性名不一致,必須要寫成下面這樣,

let {foo:baz} = {bar: "bbb", foo:"aaa"};
baz //"aaa"

let obj = {first: "hello", last: "world"};
let {firt: f, last: l} = obj;
f // "hello"
l // "world"
複製程式碼

重點來了,實際上,物件的解構賦值是下面的簡寫

 let {foo: foo, bar: bar} = {foo:"aaa", bar: "bbb"};
複製程式碼

別名

也就是說,物件的解構賦值的內部機制是先找到同名屬性,然後再賦值給對應的變數。真正被賦值的是後者,而不是前者。

 let {foo:baz} = {bar: "bbb", foo:"aaa"};
 baz //"aaa"
 foo // foo is not defined
複製程式碼
上面的程式碼中,***foo是匹配的模式,baz才是變數。真正被賦值的是變數baz,而不是模式foo。*** 

與陣列一樣,解構也可以用於對巢狀結構的物件。
複製程式碼
 let obj = {
    p:[
       'hello',
       {y: 'world'}
    ]
 };
  let {p: [x,{y}]} = obj;
  x // "hello"
  y // "world"
複製程式碼

注意這時,p是模式,不是變數,因此不會被賦值,如果p也要作為變數被賦值,可以寫成下面這樣,

  let obj = {
      p:[
          'hello',
          {y: 'world'}
      ]
   };
  let {p,p: [x,{y}]} = obj;
   x // "hello"
   y // "world"
   p // p:['hello', {y: 'world'}]
複製程式碼

另一個例子,

  var node = {
     loc:{
         start : {
             line: 1,
             column: 5
         }
     }
  };
 var {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都是模式。

物件的變數賦值

如果將一個已宣告的變數進行賦值時,一定要小心了!

//錯誤寫法
  let x
  {x} = {x:1};
//syntaxError: syntax error
複製程式碼

上面的程式碼會報錯,因為JavaScript引擎會將{x}理解成一個程式碼塊,從而發生預發錯誤。 只要不把{}寫在首行,就會解決這個問題。

let {x} = {x:1};
//或者
let x;
({x} = {x:1})
//也可以
{(x) = {x:1}}
複製程式碼

關於圓括號的使用,保證一個原則,不瞎用。 具體請參考阮一峰大神的es6標準,很全面!總結一下就是,

字串的解構賦值

例子

const [a,b,c,d,e] = 'hello'
複製程式碼

數字和布林型別的解構賦值

解構賦值時,如果等號右邊是數值和布林值,則會先轉為物件。

let {toString : s} = 123;
s === Number.prototype.toString //true
let {toString : s} = true;
s === Boolean.prototype.toString //true
複製程式碼

上面的程式碼中,數字和布林值的包裝物件都有toString屬性,因此變數s都能取到值。 解構賦值的規則是,只要等號右邊的值不是物件或者陣列,就先將其轉為物件,由於undefined和null無法轉換為物件,所以進行解構賦值時會報錯。

用途

交換變數的值

  //ES5
  var t;
  t = a;
  a = b;
  b = t;

 //ES6
 let x = 1;
 let y = 2;
 [x, y] = [y, x];
複製程式碼

從函式返回多個值

函式只能返回一個值,如果要返回多個值,只能將他們放在陣列或者物件中,有了解構賦值,取出這些值就很方便了。

  //返回一個陣列
  function example() {
    return [1,2,3,4]
  }
 let [a,b,c,d] = example();
 //如果沒有解構賦值,還得迴圈遍歷,分別賦值。
 
 //返回一個物件
  function example() {
    return {
        foo: 1,
        bar: 2
    };
  }
  let {foo, bar} = example();
複製程式碼

函式引數的定義

解構賦值可以方便地將一組引數與變數對應起來

  //引數是一組有次序的值
  function f([x,y,z]) {}
  f([1,2,3])
  //引數是一組有序的值
  function f({x,y,z}) {}
  f({x:1,z:2,x:3});
複製程式碼

提取JSON中的資料

 let jsonData = {
    id:23,
    state:"draft",
    data:{
        name:"hansheng",
        age: 18
    }
 }
 let {id,state,data:info} = jsonData;
  // 23,"draft", {name:"hansheng",age: 18}
複製程式碼

本文摘錄自阮一峰ES6標準入門。

相關文章