1. 數組的解構賦值 解構: 中允許按照一定的模式從數組和對象中提取值,然後對變數進行賦值,這被稱為解構(Destructuring). 1. 基本用法 本質上,這種寫法屬於“模式匹配”,只要等號兩邊的模式相同,左邊的變數就會被賦予相應的值。 完全解構 不完全解構 等號左邊只匹配到等號右邊的一部分。 ...
1. 數組的解構賦值
解構: ES6
中允許按照一定的模式從數組和對象中提取值,然後對變數進行賦值,這被稱為解構(Destructuring).
1. 基本用法
本質上,這種寫法屬於“模式匹配”,只要等號兩邊的模式相同,左邊的變數就會被賦予相應的值。
- 完全解構
let [a, b, c] = [1, 2, 3];
console.log(a); // 輸出1
let [a, b] = [1];
console.log(b); // 解構不成功時返回undefined
不完全解構
等號左邊只匹配到等號右邊的一部分。
let [a, [b], c] = [1, [2, 3], 4]; console.log(b); // 輸出2,只匹配到右邊的右邊數組[2, 3]中的2
不能解構情況
如果等號右邊不是可遍歷的解構,或者說等號右邊的值或是轉換為對象以後也不具備
Iterator
介面,那麼就會解構失敗。let [a] = 1; let [a] = false; let [a] = NaN; let [a] = undefined; let [a] = null; let [a] = {}
總結:事實上,只要某種數據結構具有
Iterator
介面,都可以採用數組形式的解構賦值。
2. 預設值
解構賦值允許指定預設值。
ES6內部使用嚴格相等運算符(===)判斷數組的某個位置是否有值。所以,如果一個數組成員不嚴格等於undefined,預設值是不會生效的。
let [a = 2] = [null];
// a = null
let [a = 2] = [];
// a = 2
如果預設值是一個表達式,那麼這個表達式是惰性求值的
function f() {
return 2;
}
let [a = f()] = [1];
// a = 1, f函數不會執行
let [a = f()] = [];
// a = 2, f函數會執行
預設值可以引用解構賦值的其它變數,但是該變數必須已經聲明
let [x = 1, y = x] = [];
// x = 1, y = 1
let [x = y, y = 1] = [];
// ReferenceError,在使用變數y時還並沒有聲明
2. 對象的解構賦值
對象解構賦值的內部機制是先找到同名屬性,然後再賦值給對應的變數,真正被賦值的是後者而不是前者
舉例說明:
let {foo:bar} = {foo:"aaa"};
console.log(bar); // bar = "aaa"
console.log(foo); // ReferenceError, foo is not defined
console.log({foo:bar}.foo); // {foo:bar}.foo = "aaa"
上面代碼中 foo
是匹配的模式,通過 foo
匹配到對象 {foo:bar}
中 foo
屬性的值( bar
變數),然後將值賦給 bar
變數,這樣對象 {foo:bar}
中 foo
屬性就有值了,即 bar
的值 "aaa"
.
// 實際上
let {foo1, foo2} = {foo1:"aaa", foo2:"bbb"};
// 是下麵語句的簡寫形式
let {foo1:a, foo2:b} = {foo1:"aaa", foo2:"bbb"};
對象的解構也可以指定預設值,預設值生效的條件是,對象的屬性值嚴格等於undefined
let {x = 3} = {x: undefined};
// x = 3
// 上面的語句等價於
let {x:x = 3} = {x: undefined};
// x = 3
// {x:x = 3} = 3
let {x = 3} = {x: null};
// x = null
如果解構模式是嵌套的對象,而且子對象所在的父屬性不存在,那麼將會報錯
let {foo: {bar}} = {bar: 'bar'};
// 報錯,因為foo = {bar} = undefined,{bar}對象中的bar屬性在解構時會報錯,因為{bar}是undefined,undefined是不能轉換為對象的,對undefined取bar屬性會報錯。
// 和下麵的代碼原理一樣:
let obj = {bar: "bar"};
obj.foo.bar // 報錯,因為obj.foo = undefined,對undefined取屬性會報錯。
"bar".foo // undefined,不會報錯,因為字元串可以轉換為對象
在將已經聲明的變數進行解構賦值時,要註意解構賦值語句不能直接寫在行首
let x;
{x} = {x: 1}; // 報錯,這個地方不是很懂
// 書上的解釋是:JS引擎將{x}理解成一個代碼塊,從而發生語法錯誤。
// 若想避免這個錯誤,可以這樣寫:
({x} = {x: 1});
3. 字元串的解構賦值
字元串可以解構賦值是因為字元串可以轉換成一個類似數組的包裝對象。
let [a, b, c, d, e] = "hello";
// a = 'h' b = 'e' c = 'l' d = 'l' e = 'o'
由於類似數組的包裝對象有一個 length
屬性,因此在解構賦值時可以利用這個屬性。
let {length: len} = 'hello';
// len = 5
4. 數值和布爾值的解構賦值
解構賦值時,如果等號右邊是數值或布爾值,則會先轉為對象
let {toString: s} = 123;
console.log(s === Number.prototype.toString) // true
數值 123
被轉換為對象,數值對象中有 toString
方法,與 toString
匹配,變數 s
中存儲的是 toString
方法,該方法就是 Number
原型對象中的 toString
方法。
5. 函數參數的解構賦值
函數參數的解構也可以使用預設值
function f({x = 0, y = 0} = {}) {
return {x, y};
}
console.log(f({x: undefined, y: 1}));
// 輸出: {0, 1}
詳細過程:調用函數f後,{x = 0, y = 0}={x:undefined,y:1}
由於x是undefined,故x使用預設值即x=0,由於y:1故通過解構賦值後y=1.
之前的誤區:註意是解構賦值,並不是對象之間的賦值。
再看下麵這個例子:
function f({x, y} = {x: 0, y: 0}) {
return {x, y};
}
console.log(f({x: undefined, y: 1}));
// 輸出: {undefined, 1}
// 調用函數後,{x,y}={x:udefined,y:1},解構賦值後x=undefined,y=1
console.log(f({x: 1, y: 1}));
// 輸出: {1, 1}
// 調用函數後,{x,y}={x:1,y:1},解構賦值後x=1,y=1
console.log(f({}));
// 輸出: {undefined, undefined}
// 調用函數後,{x,y}={}={undefined,undefined},解構賦值後x=undefined,y=undefined
console.log(f());
// 輸出: {0, 0} 當不傳遞實參時,形參的值是原有的值
// 調用函數後,{x,y}={x:0,y:0},解構賦值後x=0,y=0
再次重申一下 {x,y}={x:1,y:2}
這種形式是解構賦值,千萬不要理解成對象之間的賦值!
6. 圓括弧問題
對於編譯器而言,一個式子到底是模式還是表達式,沒有辦法一開始就知道,必須解析到(或解析不到)等號才能知道
不能使用圓括弧的情況
- 變數聲明語句
- 函數參數
- 賦值語句表達式
可以使用圓括弧的情況
賦值語句中的非模式部分可以使用圓括弧。
[(a)] = [3]; // a並不是模式 ({ p: (d) } = {}); // p是模式,d並不是模式
建議:無論什麼情況都儘量不要在模式中使用圓括弧
7. 解構賦值的作用
交換變數的值
let a = 1; let b = 2; [a, b] = [b, a];
方便處理函數返回值
function f([x, y]) { return [x+1, y+2]; } let [a, b] = f([1, 1]);
函數參數定義
解構賦值可以方便地將一組參數與變數名對應起來。
function f1({x, y, z}){ return x+y+z; } f1({y: 1, z: 2, x: 3 }); // 可以做到實參沒有次序
提取JSON數據
可以很方便地從JSON中提取需要的數據。
let JSON = { name: "happyCoding1024", age: 18, hobby: "coding" } let [name, age, hobby] = JSON;
函數參數預設值
非常簡化方便地使用函數參數預設值。
function f([x=0,y=0] = []){ return x+y; } f([]); // 當傳入的是undefined時,就會使用預設值
遍歷Map結構
任何部署了
Iterator
介面的對象都可以用for...of
迴圈遍歷。Map
結構原生支持Iterator
介面,配合變數的解構賦值獲取鍵名和鍵值非常方便。let map = new Map(); map.set('first', 'hello'); map.set('second', 'world'); for (let [key, value] = map){ console.log(key + "is" + value); }
輸入模塊的指定方法
載入模塊時,往往需要制定輸入的方法,解構賦值使得輸入語句非常清晰。
const {SourceMapConsumer, SourceNode} = require("source-map");