对象、数组复制的几种方法(深拷贝)

工作所需,我这个后端程序猿又开始操起我那半吊子的前端技术,开始写前端。这次在实现前端页面逻辑的时候,碰到了一个JS深拷贝的问题。原先默认都是浅拷贝,或者是第一层深拷贝,但是到里面的嵌套对象就是浅拷贝了。踩了这个坑之后,遂记录本文以供后续参考。

本文重点介绍JS中如何实现深拷贝,避免在实际开发中踩坑。当然,考虑到部分同学对深拷贝和浅拷贝的概念可能不太熟,我们也会先介绍一下深拷贝和浅拷贝的原理。

深拷贝 VS 浅拷贝

数据类型

首先我们了解下两种数据类型:

  1. 基本类型:像Number、String、Boolean等这种为基本类型
  2. 复杂类型:Object和Array

浅拷贝与深拷贝的概念

接着我们分别来了解下浅拷贝和深拷贝,深拷贝和浅拷贝是只针对Object和Array这样的复杂类型的。

浅拷贝

1
2
3
4
5
6
7
var a = {
myname: 'yana'
};
var b = a;
b.myname = '小雅';
console.log(b.myname); // 小雅
console.log(a.myname); // 小雅
1
2
3
4
5
var a = ['myname', 'yana'];
var b = a;
b[1] = '小雅';
console.log(a); // ["myname", "小雅"]
console.log(b); // ["myname", "小雅"]

可以看出,对于对象或数组类型,当我们将a赋值给b,然后更改b中的属性,a也会随着变化。

也就是说a和b指向了同一块内存,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝。

JavaScript 并没有直接提供对象或者数组的复制方法,默认采用浅拷贝。

深拷贝

刚刚我们了解了什么是浅拷贝,那么相应的,如果给b放到新的内存中,将a的各个属性都复制到新内存里,就是深拷贝。也就是说,当b中的属性有变化的时候,a内的属性不会发生变化。

如何实现深拷贝

对象的深拷贝

通过序列化实现深拷贝

我们可以将对象序列化再解析回来实现对象拷贝(注意:对象中的函数 function 不会被复制

1
2
3
4
5
var a = {v1:1, v2:2};
var b = JSON.parse(JSON.stringify(a));
b.v1 = 3;
console.log("对象a:",a);
console.log("对象b:",b);

通过递归遍历实现

  1. 我们定义一个 clone 方法实现深度复制功能(Deep Copy),其内部实现原理就是将对象的属性遍历一遍,赋给一个新的对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //自定义的复制方法
    function clone(obj) {
    var copy = {};
    for (var attr in obj) {
    copy[attr] = typeof(obj[attr])==='object' ? clone(obj[attr]) : obj[attr];
    }
    return copy;
    }

    //测试样例
    var a = {v1:1, v2:2};
    var b = clone(a);
    b.v1 = 3;
    console.log("对象a:",a);
    console.log("对象b:",b);
  2. 也可以直接给 Object 增加个 clone 方法,其内部实现原来同上面是一样的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //自定义的复制方法
    Object.prototype.clone = function() {
    var copy = (this instanceof Array) ? [] : {};
    for (var attr in this) {
    if (this.hasOwnProperty(attr)){
    copy[attr] = typeof(this[attr])==='object' ? clone(this[attr]) : this[attr];
    }
    }
    return copy;
    };

    //测试样例
    var a = {v1:1, v2:2};
    var b = a.clone();
    b.v1 = 3;
    console.log("对象a:",a);
    console.log("对象b:",b);

使用 jQuery 复制

jQuery 自带的 extend 方法可以用来实现对象的复制。

1
2
3
4
5
6
var a = {v1:1, v2:2};
var b = {};
$.extend(b,a);
b.v1 = 3;
console.log("对象a:",a);
console.log("对象b:",b);

数组的拷贝

使用 slice 方法实现

1
2
3
4
5
var a = [1, 2];
var b = a.slice(0);
b.push(3);
console.log("数组a:", a);
console.log("数组b:", b);

使用 concat 方法实现

1
2
3
4
5
var a = [1, 2];
var b = a.concat();
b.push(3);
console.log("数组a:", a);
console.log("数组b:", b);