JavaScript中十種一步拷貝陣列的方法

前端啟航發表於2019-04-24

JavaScript中十種一步拷貝陣列的方法

JavaScript中我們經常會遇到拷貝陣列的場景,但是都有哪些方式能夠來實現呢,我們不妨來梳理一下。

1、擴充套件運算子(淺拷貝)

自從ES6出現以來,這已經成為最流行的方法。它是一個很簡單的語法,但是當你在使用類似於React和Redux這類庫時,你會發現它是非常非常有用的。

numbers = [1, 2, 3];
numbersCopy = [...numbers];
複製程式碼

這個方法不能有效的拷貝多維陣列。陣列/物件值的拷貝是通過引用而不是值複製。

// 
numbersCopy.push(4);
console.log(numbers, numbersCopy);
// [1, 2, 3] and [1, 2, 3, 4]
// 只修改了我們希望修改的,原陣列不受影響

// 
nestedNumbers = [[1], [2]];
numbersCopy = [...nestedNumbers];
numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1, 300], [2]]
// [[1, 300], [2]]


複製程式碼

2、for()迴圈(淺拷貝)

考慮到函數語言程式設計變得越來越流行,我認為這種方法可能是最不受歡迎的。


numbers = [1, 2, 3];
numbersCopy = [];
for (i = 0; i < numbers.length; i++) {
  numbersCopy[i] = numbers[i];
}

複製程式碼

這個方法不能有效的拷貝多維陣列。因為我們使用的是=運算子,它在處理陣列/物件值的拷貝時通過引用而不是值複製。

// 
numbersCopy.push(4);
console.log(numbers, numbersCopy);
// [1, 2, 3] and [1, 2, 3, 4]

// 
nestedNumbers = [[1], [2]];
numbersCopy = [];
for (i = 0; i < nestedNumbers.length; i++) {
  numbersCopy[i] = nestedNumbers[i];
}
numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1, 300], [2]]
// [[1, 300], [2]]


複製程式碼

3、while()迴圈(淺拷貝)

for() 類似。

numbers = [1, 2, 3];
numbersCopy = [];
i = -1;
while (++i < numbers.length) {
  numbersCopy[i] = numbers[i];
}
複製程式碼

4、Array.map(淺拷貝)

上面的forwhile都是很“古老”的方式,讓我們繼續回到當前,我們會發現map方法。map源於數學,是將一個集合轉換成另一種集合,同時保留結構的概念。

在英語中,它意味著Array.map 每次返回相同長度的陣列。

numbers = [1, 2, 3];
double = (x) => x * 2;

numbers.map(double);
複製程式碼

當我們使用map方法時,需要給出一個callback函式用於處理當前的陣列,並返回一個新的陣列元素。

和拷貝陣列有什麼關係呢?

當我們想要複製一個陣列的時候,只需要在mapcallback函式中直接返回原陣列的元素即可。

numbers = [1, 2, 3];
numbersCopy = numbers.map((x) => x);
複製程式碼

如果你想更數學化一點,(x) => x叫做恆等式。它返回給定的任何引數。

identity = (x) => x;
numbers.map(identity);
// [1, 2, 3]
複製程式碼

同樣的,處理物件和陣列的時候是引用而不是值複製。

5、Array.filter(淺拷貝)

Array.filter方法同樣會返回一個新陣列,但是並不一定是返回同樣長度的,這和我們的過濾條件有關。

[1, 2, 3].filter((x) => x % 2 === 0)
// [2]

複製程式碼

當我們的過濾條件總是true時,就可以用來實現拷貝。

numbers = [1, 2, 3];
numbersCopy = numbers.filter(() => true);
// [1, 2, 3]
複製程式碼

同樣的,處理物件和陣列的時候是引用而不是值複製。

6、Array.reduce(淺拷貝)

其實用reduce來拷貝陣列並沒有展示出它的實際功能,但是我們還是要將其能夠拷貝陣列的能力說一下的

numbers = [1, 2, 3];
numbersCopy = numbers.reduce((newArray, element) => {
  newArray.push(element);
  return newArray;
}, []);
複製程式碼

reduce() 方法對陣列中的每個元素執行一個由您提供的reducer函式,將其結果彙總為單個返回值。

上面我們的例子中初始值是一個空陣列,我們在遍歷原陣列的時候來填充這個空陣列。該陣列必須要從下一個迭代函式的執行後被返回出來。

同樣的,處理物件和陣列的時候是引用而不是值複製。

7、Array.slice(淺拷貝)

slice 方法根據我們指定的start、end的index從原陣列中返回一個淺拷貝的陣列。

[1, 2, 3, 4, 5].slice(0, 3);
// [1, 2, 3]
// Starts at index 0, stops at index 3

// 當不給定引數時,就返回了原陣列的拷貝
numbers = [1, 2, 3, 4, 5];
numbersCopy = numbers.slice();
// [1, 2, 3, 4, 5]
複製程式碼

同樣的,處理物件和陣列的時候是引用而不是值複製。

8、JSON.parse & JSON.stringify(深拷貝)

JSON.stringify將一個物件轉成字串; JSON.parse將轉成的字串轉回物件。

將它們組合起來可以將物件轉換成字串,然後反轉這個過程來建立一個全新的資料結構。

nestedNumbers = [[1], [2]];
numbersCopy = JSON.parse(
  JSON.stringify(nestedNumbers)
);
numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1], [2]]
// [[1, 300], [2]]
// These two arrays are completely separate!
複製程式碼

這個可以安全地拷貝深度巢狀的物件/陣列

幾種特殊情況

1、如果obj裡面有時間物件,則JSON.stringify後再JSON.parse的結果,時間將只是字串的形式。而不是時間物件;

var test = {
  name: 'a',
  date: [new Date(1536627600000), new Date(1540047600000)],
};

let b;
b = JSON.parse(JSON.stringify(test))
console.log(b)
複製程式碼

2、如果obj裡有RegExp、Error物件,則序列化的結果將只得到空物件;

const test = {
  name: 'a',
  date: new RegExp('\\w+'),
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'
console.log('ddd', test, copyed)
複製程式碼

3、如果obj裡有函式,undefined,則序列化的結果會把函式或 undefined丟失;

const test = {
  name: 'a',
  date: function hehe() {
    console.log('fff')
  },
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'
console.error('ddd', test, copyed)
複製程式碼

4、如果obj裡有NaN、Infinity和-Infinity,則序列化的結果會變成null

5、JSON.stringify()只能序列化物件的可列舉的自有屬性,例如 如果obj中的物件是有建構函式生成的, 則使用JSON.parse(JSON.stringify(obj))深拷貝後,會丟棄物件的constructor;

function Person(name) {
  this.name = name;
  console.log(name)
}

const liai = new Person('liai');

const test = {
  name: 'a',
  date: liai,
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'
console.error('ddd', test, copyed)
複製程式碼

參考文章:關於JSON.parse(JSON.stringify(obj))實現深拷貝應該注意的坑

9、Array.concat(淺拷貝)

concat將陣列與值或其他陣列進行組合。

[1, 2, 3].concat(4); // [1, 2, 3, 4]
[1, 2, 3].concat([4, 5]); // [1, 2, 3, 4, 5]
複製程式碼

如果我們不指定引數或者提供一個空陣列作為引數,就可以進行淺拷貝。

[1, 2, 3].concat(); // [1, 2, 3]
[1, 2, 3].concat([]); // [1, 2, 3]
複製程式碼

同樣的,處理物件和陣列的時候是引用而不是值複製。

10、Array.from(淺拷貝)

可以將任何可迭代物件轉換為陣列。給一個陣列返回一個淺拷貝。

console.log(Array.from('foo'))
// ['f', 'o', 'o']

numbers = [1, 2, 3];
numbersCopy = Array.from(numbers)
// [1, 2, 3]
複製程式碼

同樣的,處理物件和陣列的時候是引用而不是值複製。


小結

上面這些方法都是在使用一個步驟來進行拷貝。如果我們結合一些其他的方法或技術能夠發現還有很多的方式來實現陣列的拷貝,比如一系列的拷貝工具函式等。

相關文章