JS的浅拷贝和深拷贝

661 阅读4分钟

一、问题

在日常开发中,会有复制一个对象,当改变新的对象时,原来的对象也发生了变化的情况。我们不希望出现这样的问题,那么可以用浅拷贝或深拷贝来解决这种情况。让我们带着一些问题来理解浅拷贝和深拷贝:

1、什么是浅拷贝?什么是深拷贝?

2、浅拷贝和深拷贝的区别?

3、如何实现浅拷贝或深拷贝?

二、认识深拷贝和浅拷贝

先来看一下下面的代码:

var a = 1;
var b = a;
b = 2;
console.log(a, b); // 1 2

var obj1 = {
    a: 1
};
var obj2 = obj1;
obj2.a = 2;
console.log(obj1.a, obj2.a); // 2 2

我们知道,JavaScript中有两大数据类型:分别是值类型和对象类型。值类型是没有深拷贝和浅拷贝这个概念的,这个和它们存储方式有关。值类型的数据是存储在栈内存中,按值访问;而对象类型的数据是存储在堆内存中,按引用(地址、指针)访问。

对象类型的数据直接赋值后会共用一个引用,改变其中一个都会影响到另一个,深拷贝和浅拷贝就是为解决这类对象直接赋值后依然“连接”的问题。那么就可以回答第一个问题,什么是浅拷贝和深拷贝了。

浅拷贝:复制一层对象的属性,并不包括对象里面是引用类型的数据,当改变拷贝的对象里面的引用类型时,源对象也会改变。

深拷贝:重新开辟一个内存空间,需要递归拷贝对象里的引用,直到子属性都为基本类型。两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。

第二个问题,它们的区别:首先深拷贝和浅拷贝只针对如Object,Array这样的复杂对象的。深拷贝和浅拷贝的“深浅”主要针对的是对象的“深度”,简单来说,浅拷贝只复制一层对象的属性,而深拷贝则递归复制了所有层级。

三、浅拷贝的实现

1、Object.assign()

Object.assign()方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

var obj1 = {
    a: 1,
}
var obj2 = Object.assign({}, obj1);
obj2.a = 2;
console.log(obj1.a); // 1

2、通过展开运算符 ... 来实现

var obj1 = {
    a: 1,
}
var obj2 = {...obj1};
obj2.a = 2;
console.log(obj1.a); // 1

3、简单手写实现

下面来简单的实现一下浅拷贝:

function copy(obj) {
    var target = {};
    for (key in obj) {
        target[key] = obj[key];
    }
    return target;
}
var obj1 = {
    a: 1,
    b: {
        c: '我就是我,颜色不一样的烟火!',
    },
}
var obj2 = copy(obj1);
obj2.a = 2;
console.log(obj1.a); // 1

以上便是浅拷贝的简单实现,拷贝完成后更改obj2.a 的值,但是obj1.a的值没有发生改变,说明实现了浅拷贝。

浅拷贝可以解决常见的问题,但是如果不常见呢,比如说对象里面还有子对象,那么用浅拷贝就不够彻底;比如:

var obj3 = copy(obj1);
obj3.b.c = '你被我改变了吧!哈哈';
console.log(obj1.b.c); // 你被我改变了吧!哈哈

上面代码中,拷贝完成后更改了obj3.b.c,结果obj1.b.c也随之改变,说明子对象b依然存在共用同一个引用的现象,所以浅拷贝拷贝的并不彻底。这时候深拷贝就该上场了,它能用递归的思想继续深挖,直到最底层为止。

四、深拷贝的实现

1、JSON.parse(JSON.stringify(object))

用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象

var obj1 = {
    a: 1,
    b: {
        c: '我就是我,颜色不一样的烟火!',
        d: undefined,
    }
}
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.c = '我承认我有赌的成分!';
obj2.b.d = '来了,老弟!',
console.log(obj1.b.c); // 我就是我,颜色不一样的烟火!
console.log(obj1.b.d); // undefined

这个方法使用较为简单,可以满足基本的深拷贝需求,而且能够处理JSON格式能表示的所有数据类型。但是这个方法有一定的局限性:对于undefinedsymbol、正则表达式类型、函数类型等数据无法进行深拷贝(而且会丢失相应的值);它会抛弃对象的constructor,即深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object;无法正确处理对象中存在循环引用的情况。

2、jQuery的extend方法

var obj3 = $.extend(true, {}, obj1);
obj3.b.c = '你有没有见过一招从天而降的掌法?';
console.log(obj1.b.c); // 我就是我,颜色不一样的烟火!

第一个参数为true则说明要做深拷贝的操作,为false则是做浅拷贝的操作。

3、递归拷贝实现

下面用递归简单的实现一下深拷贝:

function deepCopy(obj) {
    var target = isArray(obj) ? [] : {};
    for (key in obj) {
        var copy = obj[key];
        if (isObject(copy) || isArray(copy)) {
            target[key] = deepCopy(copy); // 核心代码
        } else {
            target[key] = copy;
        }
    }
    return target;
}
function isObject(obj) {
    return toString.call(obj) === '[object Object]';
}
function isArray(arr) {
    return toString.call(arr) === '[object Array]';
}
var obj4 = deepCopy(obj1);
obj4.b.c = '我摊牌了!';
console.log(obj1.b.c); // 我就是我,颜色不一样的烟火!

深拷贝是拷贝得非常彻底的,可以做到真正意义上的杜绝共用一个引用的问题。