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.cancat(浅拷贝)

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]

同样的,处理对象和数组的时候是引用而不是值复制。


小结

上面这些方法都是在使用一个步骤来进行拷贝。如果我们结合一些其他的方法或技术能够发现还有很多的方式来实现数组的拷贝,比如一系列的拷贝工具函数等。


原文地址:

How to clone an array in JavaScript


关注微信公众号同步推送更新

或者可以去Github上面给个Star

原文地址:https://www.cnblogs.com/ld1024/p/10750162.html

时间: 2024-08-24 11:47:35

JavaScript中十种一步拷贝数组的方法的相关文章

在JavaScript中判断整型的N种方法

整数类型(Integer)在JavaScript经常会导致一些奇怪的问题.在ECMAScript的规范中,他们只存在于概念中: 所有的数字都是浮点数,并且整数只是没有一组没有小数的数字. 在这篇博客中,我会解释如何去检查某个值是否为整型. ECMAScript 5 在ES5中有很多方法你可以使用.有时侯,你可能想用自己的方法:一个isInteger(x)的函数,如果是整型返回true,否则返回false. 让我们看看一些例子. 通过余数检查 你可以使用余数运算(%),将一个数字按1求余,看看余数

javascript中定义声明函数的四种方法

javascript中定义声明函数的四种方法 :http://blog.163.com/zzf_fly/blog/static/209589158201286104927248/ 方法一:function functionName([parameters]){functionBody}; 方法二:将一个未命名的函数function赋给一个指定变量(var):var add=function(a, b){} 方法三:使用new运算符声明函数varName=new Function([param1N

JavaScript中如何判断变量是数组、函数或是对象类型

数组 ECMAScript5中Array.isArray是原生的判断数组的方法,IE9及以上支持.考虑到兼容性,在没有此方法的浏览器中,可以使用 Object.prototype.toString.call(obj) === '[object Array]'替代. var isArray = Array.isArray || function(obj) {     return Object.prototype.toString.call(obj) === '[object Array]'; }

JavaScript中如何判断变量是数组、函数还是对象类型

数组 ECMAScript5中Array.isArray是原生的判断数组的方法,IE9及以上支持.考虑到兼容性,在没有此方法的浏览器中,可以使用Object.prototype.toString.call(obj) === '[object Array]'替代. var isArray = Array.isArray || function(obj) { return Object.prototype.toString.call(obj) === '[object Array]'; } 函数 最

JavaScript中函数对象的call()和apply()方法的总结

在JavaScript中,每个函数都有call()和apply()方法,用法举例如下: 先定义一个some_obj的对象,该对象有一个say()方法: var some_obj={ name:'Ninja', say:function(who){ return 'Haya ' + who + ', I am a '+ this.name; } }; 调用some_obj.say('Dude'); 输出:"Haya Dude, I am a Ninja" 再创建一个对象 var my_o

JavaScript 中 for in 循环和数组的问题

本文由 伯乐在线 - ElvisKang 翻译,进林 校稿.未经许可,禁止转载!英文出处:adripofjavascript.com.欢迎加入翻译小组. JavaScript的for…in循环用于迭代访问对象中的可枚举(enumerable)属性: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var tMinus = {     two: "Two",     one: "One",     zero: "Blast

JavaScript中的window对象的属性和方法;JavaScript中如何选取文档元素

一.window对象的属性和方法 ①setTimeout()方法用来实现一个函数在指定毫秒之后运行,该方法返回一个值,这个值可以传递给clearTimeout()用于取消这个函数的执行. ②setInterval()用来实现一个在指定毫毛数的时间里重复调用,返回一个值,这               个值可以传递给clearInterval()用于取消后续函数的调用. ③Document对象的location属性也引用到Location对象: window.location === docume

javascript中slice(),splice(),split(),substring(),substr()使用方法

1.slice(): Array和String对象都有 在Array中  slice(i,[j]) i为开始截取的索引值,负数代表从末尾算起的索引值,-1为倒数第一个元素j为结束的索引值,缺省时则获取从i到末尾的所有元素 参数返回:返回索引值从i到j的数组,原数组不改变 在String中 slice(i,[j]) 参数说明:i为开始截取的索引值,负数代表从末尾算起的索引值,-1为倒数第一个字符j为结束的索引值,缺省时则获取从i到末尾的所有字符 2.splice() 存在Array中     方法

JavaScript中的call、apply、bind方法的区别

在JS中,this的指向是动态变化的,很可能在写程序的过程中,无意中破坏掉this的指向,所以我们需要一种可以把this的含义固定的技术,于是就有了call,apply和bind这三个方法 apply:应用某一对象的一个方法,用另一个对象替换当前对象.call:调用一个对象的一个方法,以另一个对象替换当前对象. △它们的共同之处:都可以用来代替另一个对象调用一个方法,将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象. △它们的不同之处:①apply:最多只能有两个参数—