iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 3
0
自我挑戰組

玩轉 React 從0到1系列 第 3

【Day 3】關於ES6展開/其餘運算符與物件拷貝

  • 分享至 

  • xImage
  •  

展開運算符 與 其餘參數運算符

在 ES6 中,新增了 展開運算符(Spread Operator)其餘參數運算符(Rest Operator),但它的寫法都是...,接下來我們來介紹一下他們到底哪裡不同?

展開運算符 ( Spread Operator )

展開運算符是一個把陣列展開成個別的值的速寫語法

簡單來說,就是將陣列的值一個一個取出來,一般來說都會運用在陣列宣告(複製陣列到新的陣列中)或是函式呼叫(傳遞參數)使用。

舉例來說:

function average(a, b, c, d) {
    return (a + b + c + d)/4;
}
const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = [...arr1, ...arr2]; // [1, 2, 3, 4]  陣列宣告
average(...arr3);    // 2.5  函式呼叫
let numberArray = [1, 2, 3, 4, 5, 6];
console.log(Math.max(numberArray)); // NAN,因為傳入的是陣列
console.log(Math.max(...numberArray)); // 6,會先轉換成 1,2,3,4,5,6,再去比較 Math.max

展開運算符還有一個特別的功能,可以將可迭代(Iterable)或與陣列相似(Array-like)的物件轉換成為陣列。
內建可以迭代(Iterable)的物件有 String, Array, TypedArray, Map, Set 物件。

舉例來說:

function TransferToArray(x) {
  console.log(arguments);
  console.log(Array.isArray(arguments));
  
  //轉換成陣列
  const arr = [...arguments];
  console.log(arr);
  console.log(Array.isArray(arr));
}
const obj1 = { a: 111, b: 222 };
const obj2 = { c: 333, d: 444 };
const merged = { ...obj1, ...obj2 }; // { a: 111, b: 222, c: 333, d: 444}
TransferToArray(merged); // [object Arguments]{} , false, [[object Object]], true

其餘運算符 ( Rest Operator )

其餘運算符是 收集其餘(剩餘的)值 轉變成一個陣列

其餘運算符一般來說都會用在函式定義中的傳入參數定義中,簡單來說其餘運算字會幫助我們把輸入函式中的參數值變成陣列,稱為其餘參數(Rest Parameters),又或是運用在解構賦值時。

舉例來說:

function sum(...numbers) {
  var result = 0;
  console.log(numbers);
  numbers.forEach(function (number) {
    result += number;
  });
  return result;
}
console.log(sum()); // 0 沒傳入值時,numbers = []
console.log(sum(1, 2, 3, 4, 5)); // 15,同時傳入多值

物件拷貝

https://ithelp.ithome.com.tw/upload/images/20200918/20109963U8Q3pC99ZV.png

昨天我們有提到 JS 型別的類型分為基本型別(Primitives) 與物件型別(Object)兩大類,
這兩個型別最大的差異,就在於他們傳值的方式:

  • 基本型別 => 傳值 (value)
  • 物件型別 => 傳址 (reference)

當資料在複製時,我們來看看 基本型別 跟 物件型別 的不同,這也是 傳值 跟 傳址 的不同:

let a = 123;
let b = a;
a = 467;
console.log(a);  // 456
console.log(b);  // 123

let obj1 = {
    foo: 'test'
}
let obj2 = obj1;
obj1.foo = 'changed';
console.log(obj1.foo); // changed
console.log(obj2.foo);  // changed

下張圖是說明 物件型別 在儲存時的狀態:
https://ithelp.ithome.com.tw/upload/images/20200918/20109963lTDF1REhNl.jpg

可以看得出來它是將 {number:10} 這個 object assign 給 o,當然它可以 assign 給其他變數,但它變動時更動的是同一個記憶體位址所儲存的值,所以就連帶改變所有被 assign 變數的值。


淺拷貝 與 深拷貝

https://ithelp.ithome.com.tw/upload/images/20200918/20109963SvXh4L89Dp.png

物件拷貝其實我們又可以拆分成 淺拷貝(Shallow Copy) 與 深拷貝(Deep Copy)。

  • 淺拷貝:只能複製第一層,若有第二層以上的資料的話,就無法達到實際的複製,會與舊物件一起共用同一塊記憶體
  • 深拷貝:會另外創造一個一模一樣的物件,新物件跟原物件不共用記憶體,修改新物件不會改到原物件

淺拷貝實作

  1. Object.assign({}, obj)
  2. 展開運算符方法 obj1 = {...obj1}
  3. Object.fromEntries(Object.entries(obj))
const array1 = [['a', 0], ['b', 1]];
const obj = Object.fromEntries(array1);  // 轉成 obj {a: 0,b: 1}
const array2 = Object.entries(obj);     // 轉成 array [['a', 0], ['b', 1]]
  1. Object.create({}, Object.getOwnPropertyDescriptors(obj))
const obj = {
	a1: 123,
 	get bar() { return 'test'}
};
const obj2 = Object.create({}, Object.getOwnPropertyDescriptors(obj));
console.log(obj2);  // { a1: 123,bar: "test" }
  1. Object.defineProperties({},Object.getOwnPropertyDescriptors(obj))
const obj1 = {
	set foo(value) {
  	console.log(value);
  }
};
const obj2 = {};
Object.defineProperties(obj2, Object.getOwnPropertyDescriptors(obj1));
// { foo: undefined }
Object.getOwnPropertyDescriptor(obj2, 'foo');
// { set foo(value) {console.log(value); }

淺拷貝的問題

以展開運算符方法為例子:

let obj = {a1: 'test', b1:{number: 10}}
let objCopy = {...obj};

obj.name = 'test2';
objCopy.b1.number = 99;

console.log(obj);  //{a1: "test", b1:{number: 99}}
console.log(objCopy); //{a1: "test2", b1:{number: 99}}

可以看到因為更動 objCopy 的值而導致 obj b1 number 的值也跟著更動

深拷貝實作

const DeepClone = (obj) => {
	if (typeof obj !== 'object') return;
  let NewObj = obj instanceof Array ? [] : {}
  for (let key in obj) {
  	if (typeof obj[key] === 'object') {
    	NewObj[key] = DeepClone(obj[key]);
    } else {
    	NewObj[key] = obj[key]
    }
  }
  return NewObj;
}
let obj1 = {
	a: {b: {c: 1}}
}
const obj2 = DeepClone(obj1); // a: {b: {c: 1}}

另外也可以使用 lodash 實作

結論

  • 介紹了 ES6 展開運算符 與 其餘參數運算符
  • 介紹了 ES6 物件拷貝方法:淺拷貝 與 深拷貝
    /images/emoticon/emoticon69.gif

參考文件

JS-淺拷貝(Shallow Copy) VS 深拷貝(Deep Copy)


上一篇
【Day 2】關於ES6的資料類型,let和const
下一篇
【Day 4】關於ES6原型與原型鏈
系列文
玩轉 React 從0到130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言