1.擴展運算符(淺拷貝)
自從ES6出現以來,這已經成為最流行的方法。它是一個很簡單的語法,但是當你在使用類似於React和Redux這類庫時,你會發現它是非常非常有用的。
1 numbers = [1, 2, 3]; 2 numbersCopy = [...numbers];
- 這個方法不能有效的拷貝多維數組。數組/對象值的拷貝是通過引用而不是值復制。
1 // 😊 2 numbersCopy.push(4); 3 console.log(numbers, numbersCopy); 4 // [1, 2, 3] and [1, 2, 3, 4] 5 // 只修改了我們希望修改的,原數組不受影響 6 7 // 😢 8 nestedNumbers = [[1], [2]]; 9 numbersCopy = [...nestedNumbers]; 10 numbersCopy[0].push(300); 11 console.log(nestedNumbers, numbersCopy); 12 // [[1, 300], [2]] 13 // [[1, 300], [2]] 14 // 由於公用引用,所以兩個數組都被修改了,這是我們不希望的
2.for()循環(淺拷貝)
考慮到函數式編程變得越來越流行,我認為這種方法可能是最不受歡迎的。
1 numbers = [1, 2, 3]; 2 numbersCopy = []; 3 for (i = 0; i < numbers.length; i++) { 4 numbersCopy[i] = numbers[i]; 5 }
這個方法不能有效的拷貝多維數組。因為我們使用的是=
運算符,它在處理數組/對象值的拷貝時通過引用而不是值復制。
1 // 😊 2 numbersCopy.push(4); 3 console.log(numbers, numbersCopy); 4 // [1, 2, 3] and [1, 2, 3, 4] 5 6 // 😢 7 nestedNumbers = [[1], [2]]; 8 numbersCopy = []; 9 for (i = 0; i < nestedNumbers.length; i++) { 10 numbersCopy[i] = nestedNumbers[i]; 11 } 12 numbersCopy[0].push(300); 13 console.log(nestedNumbers, numbersCopy); 14 // [[1, 300], [2]] 15 // [[1, 300], [2]] 16 // 由於公用引用,所以兩個數組都被修改了,這是我們不希望的
3.while()循環(淺拷貝)
1 //和for() 類似。 2 3 numbers = [1, 2, 3]; 4 numbersCopy = []; 5 i = -1; 6 while (++i < numbers.length) { 7 numbersCopy[i] = numbers[i]; 8 }
4.Array.map(淺拷貝)
上面的for
和while
都是很“古老”的方式,讓我們繼續回到當前,我們會發現map
方法。map
源於數學,是將一個集合轉換成另一種集合,同時保留結構的概念。
在英語中,它意味着Array.map
每次返回相同長度的數組。
1 numbers = [1, 2, 3]; 2 double = (x) => x * 2; 3 4 numbers.map(double);
當我們使用map
方法時,需要給出一個callback
函數用於處理當前的數組,並返回一個新的數組元素。和拷貝數組有什么關系呢?
當我們想要復制一個數組的時候,只需要在map
的callback
函數中直接返回原數組的元素即可。
1 numbers = [1, 2, 3]; 2 numbersCopy = numbers.map((x) => x);
如果你想更數學化一點,(x) => x
叫做恆等式
。它返回給定的任何參數。
1 identity = (x) => x; 2 numbers.map(identity); 3 // [1, 2, 3]
同樣的,處理對象和數組的時候是引用而不是值復制。
5.Array.filter(淺拷貝)
Array.filter
方法同樣會返回一個新數組,但是並不一定是返回同樣長度的,這和我們的過濾條件有關。
1 [1, 2, 3].filter((x) => x % 2 === 0) 2 // [2]
當我們的過濾條件總是true時,就可以用來實現拷貝。
1 numbers = [1, 2, 3]; 2 numbersCopy = numbers.filter(() => true); 3 // [1, 2, 3]
同樣的,處理對象和數組的時候是引用而不是值復制。
6.Array.reduce(淺拷貝)
其實用reduce
來拷貝數組並沒有展示出它的實際功能,但是我們還是要將其能夠拷貝數組的能力說一下的
1 numbers = [1, 2, 3]; 2 numbersCopy = numbers.reduce((newArray, element) => { 3 newArray.push(element); 4 return newArray; 5 }, []);
reduce()
方法對數組中的每個元素執行一個由您提供的reducer
函數,將其結果匯總為單個返回值。
上面我們的例子中初始值是一個空數組,我們在遍歷原數組的時候來填充這個空數組。該數組必須要從下一個迭代函數的執行后被返回出來。
同樣的,處理對象和數組的時候是引用而不是值復制。
7.Array.slice(淺拷貝)
slice
方法根據我們指定的start、end的index從原數組中返回一個淺拷貝的數組。
1 [1, 2, 3, 4, 5].slice(0, 3); 2 // [1, 2, 3] 3 // Starts at index 0, stops at index 3 4 5 // 當不給定參數時,就返回了原數組的拷貝 6 numbers = [1, 2, 3, 4, 5]; 7 numbersCopy = numbers.slice(); 8 // [1, 2, 3, 4, 5]
同樣的,處理對象和數組的時候是引用而不是值復制。
8.JSON.parse & JSON.stringify(深拷貝)
JSON.stringify
將一個對象轉成字符串;JSON.parse
將轉成的字符串轉回對象。
將它們組合起來可以將對象轉換成字符串,然后反轉這個過程來創建一個全新的數據結構。
1 nestedNumbers = [[1], [2]]; 2 numbersCopy = JSON.parse( 3 JSON.stringify(nestedNumbers) 4 ); 5 numbersCopy[0].push(300); 6 console.log(nestedNumbers, numbersCopy); 7 // [[1], [2]] 8 // [[1, 300], [2]] 9 // These two arrays are completely separate!
這個可以安全地拷貝深度嵌套的對象/數組
幾種特殊情況
1、如果obj里面有時間對象,則JSON.stringify后再JSON.parse的結果,時間將只是字符串的形式。而不是時間對象;
1 var test = { 2 name: 'a', 3 date: [new Date(1536627600000), new Date(1540047600000)], 4 }; 5 6 let b; 7 b = JSON.parse(JSON.stringify(test)) 8 console.log(b)
2、如果obj里有RegExp、Error對象,則序列化的結果將只得到空對象;
1 const test = { 2 name: 'a', 3 date: new RegExp('\\w+'), 4 }; 5 // debugger 6 const copyed = JSON.parse(JSON.stringify(test)); 7 test.name = 'test' 8 console.log('ddd', test, copyed)
3、如果obj里有函數,undefined,則序列化的結果會把函數或 undefined丟失;
1 const test = { 2 name: 'a', 3 date: function hehe() { 4 console.log('fff') 5 }, 6 }; 7 // debugger 8 const copyed = JSON.parse(JSON.stringify(test)); 9 test.name = 'test' 10 console.error('ddd', test, copyed)
4、如果obj里有NaN、Infinity和-Infinity,則序列化的結果會變成null
5、JSON.stringify()只能序列化對象的可枚舉的自有屬性,例如 如果obj中的對象是有構造函數生成的, 則使用JSON.parse(JSON.stringify(obj))深拷貝后,會丟棄對象的constructor;
1 function Person(name) { 2 this.name = name; 3 console.log(name) 4 } 5 6 const liai = new Person('liai'); 7 8 const test = { 9 name: 'a', 10 date: liai, 11 }; 12 // debugger 13 const copyed = JSON.parse(JSON.stringify(test)); 14 test.name = 'test' 15 console.error('ddd', test, copyed)
參考文章:關於JSON.parse(JSON.stringify(obj))實現深拷貝應該注意的坑
9.Array.concat(淺拷貝)
concat
將數組與值或其他數組進行組合。
1 [1, 2, 3].concat(4); // [1, 2, 3, 4] 2 [1, 2, 3].concat([4, 5]); // [1, 2, 3, 4, 5]
如果我們不指定參數或者提供一個空數組作為參數,就可以進行淺拷貝。
1 [1, 2, 3].concat(); // [1, 2, 3] 2 [1, 2, 3].concat([]); // [1, 2, 3]
同樣的,處理對象和數組的時候是引用而不是值復制。
10.Array.from(淺拷貝)
可以將任何可迭代對象轉換為數組。給一個數組返回一個淺拷貝。
1 console.log(Array.from('foo')) 2 // ['f', 'o', 'o'] 3 4 numbers = [1, 2, 3]; 5 numbersCopy = Array.from(numbers) 6 // [1, 2, 3]
同樣的,處理對象和數組的時候是引用而不是值復制。
小結
上面這些方法都是在使用一個步驟來進行拷貝。如果我們結合一些其他的方法或技術能夠發現還有很多的方式來實現數組的拷貝,比如一系列的拷貝工具函數等。