天天看點

es6 javascript對象的擴充運算符

目前, ES7 有一個提案,将 Rest 解構指派 / 擴充運算符( ... )引入對象。 Babel 轉碼器已經支援這項功能。

( 1 ) Rest 解構指派

對象的 Rest 解構指派用于從一個對象取值,相當于将所有可周遊的、但尚未被讀取的屬性,配置設定到指定的對象上面。所有的鍵和它們的值,都會拷貝到新對象上面。

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
           

上面代碼中,變量z是 Rest 解構指派所在的對象。它擷取等号右邊的所有尚未讀取的鍵(a和b),将它們和它們的值拷貝過來。

由于 Rest 解構指派要求等号右邊是一個對象,是以如果等号右邊是undefined或null,就會報錯,因為它們無法轉為對象。

let { x, y, ...z } = null; //  運作時錯誤
let { x, y, ...z } = undefined; //  運作時錯誤
//Rest 解構指派必須是最後一個參數,否則會報錯。
let { ...x, y, z } = obj; //  句法錯誤
let { x, ...y, ...z } = obj; //  句法錯誤
           

上面代碼中, Rest 解構指派不是最後一個參數,是以會報錯。

注意, Rest 解構指派的拷貝是淺拷貝,即如果一個鍵的值是複合類型的值(數組、對象、函數)、那麼 Rest 解構指派拷貝的是這個值的引用,而不是這個值的副本。

let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2
           

上面代碼中,x是 Rest 解構指派所在的對象,拷貝了對象obj的a屬性。a屬性引用了一個對象,修改這個對象的值,會影響到 Rest 解構指派對它的引用。

另外, Rest 解構指派不會拷貝繼承自原型對象的屬性。

let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let o3 = { ...o2 };
o3 // { b: 2 }
           

上面代碼中,對象o3是o2的拷貝,但是隻複制了o2自身的屬性,沒有複制它的原型對象o1的屬性。

下面是另一個例子。

var o = Object.create({ x: 1, y: 2 });
o.z = 3;
let { x, ...{ y, z } } = o;
x // 1
y // undefined
z // 3
           

上面代碼中,變量x是單純的解構指派,是以可以讀取繼承的屬性; Rest 解構指派産生的變量y和z,隻能讀取對象自身的屬性,是以隻有變量z可以指派成功。

Rest 解構指派的一個用處,是擴充某個函數的參數,引入其他操作。

function baseFunction({ a, b }) {
	// ...
}
function wrapperFunction({ x, y, ...restConfig }) {
	//  使用 x 和 y 參數進行操作
	//  其餘參數傳給原始函數
	return baseFunction(restConfig);
}
           

上面代碼中,原始函數baseFunction接受a和b作為參數,函數wrapperFunction在baseFunction的基礎上進行了擴充,能夠接受多餘的參數,并且保留原始函數的行為。

( 2 )擴充運算符

擴充運算符(...)用于取出參數對象的所有可周遊屬性,拷貝到目前對象之中。

let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
           

這等同于使用Object.assign方法。

let aClone = { ...a };
//  等同于
let aClone = Object.assign({}, a);
           

擴充運算符可以用于合并兩個對象。

let ab = { ...a, ...b };
//  等同于
let ab = Object.assign({}, a, b);
           

如果使用者自定義的屬性,放在擴充運算符後面,則擴充運算符内部的同名屬性會被覆寫掉。

let aWithOverrides = { ...a, x: 1, y: 2 };
//  等同于
let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
//  等同于
let x = 1, y = 2, aWithOverrides = { ...a, x, y };
//  等同于
let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });
           

上面代碼中,a對象的x屬性和y屬性,拷貝到新對象後會被覆寫掉。

這用來修改現有對象部分的部分屬性就很友善了。

let newVersion = {
	...previousVersion,
	name: 'New Name' // Override the name property
};
           

上面代碼中,newVersion對象自定義了name屬性,其他屬性全部複制自previousVersion對象。

如果把自定義屬性放在擴充運算符前面,就變成了設定新對象的預設屬性值。

let aWithDefaults = { x: 1, y: 2, ...a };
//  等同于
let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
//  等同于
let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);
           

擴充運算符的參數對象之中,如果有取值函數get,這個函數是會執行的。

//  并不會抛出錯誤,因為 x 屬性隻是被定義,但沒執行
let aWithXGetter = {
	...a,
	get x() {
		throws new Error('not thrown yet');
	}
};
//  會抛出錯誤,因為 x 屬性被執行了
let runtimeError = {
	...a,
	...{
		get x() {
			throws new Error('thrown now');
		}
	}
};
           

如果擴充運算符的參數是null或undefined,這個兩個值會被忽略,不會報錯。

let emptyObject = { ...null, ...undefined }; //  不報錯
           

繼續閱讀