js数据的浅拷贝与深拷贝

无论前端还是后端,经常要用到数据的拷贝,这就分成了浅拷贝与深拷贝。

浅拷贝与深拷贝区别

浅拷贝和深拷贝的区别表现在引用数据类型上,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存;但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象;所以浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制。
想要理解上面一段话,我们要搞清浏览器开辟空间存储js数据的方式,众所周知,js的数据分为基本数据类型和引用类型。

  • 基本数据类型有哪些,number,string,boolean,null,undefined,symbol以及未来ES10新增的BigInt(任意精度整数)七类。
  • 引用数据类型(Object类)有常规名值对的无序对象{x:1},数组[0,2,3],以及函数等。   


基本类型存储在栈内存中,例如 a=1,b复制a ,栈内存会新开辟一个内存
1.jpg
所以修改b的值不会影响a的值 他们互相独立;
引用数据类型:名(name)存储在栈内存中,值(val)存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值。
2.jpg
所以如上图,加入a和b都是引用数据类型,b拷贝a以后,修改b堆值会修改堆内存堆值,所以a的值也会被修改;
因此,在对引用类型数据复制时要用深拷贝,如下图:
3.jpg
深拷贝就是开辟一个新的堆内存,把复制过来的数据放到新的堆内存,这样拷贝者与被拷贝者就互不影响。

浅拷贝

使用扩展运算符(spread)或 Object.assign(...)


var a = { x: 1, y: 2 };
var b = Object.assign({}, a);
console.log(b); //{ x: 1, y: 2 }


var a = [1, 2, 3];
var b = [4, 5, 6];
var c = [...a, ...b];
console.log(c); //[1, 2, 3,4,5,6]

虽说上面两个例子被拷贝的对象都是引用类型,但是其里面的数据是值类型,所以就用浅拷贝;其实可以 理解为上述浅拷贝代码实现了层级为一层的深拷贝

深拷贝

深拷贝,是拷贝对象各个层级的属性,最通用的方法可以使用递归来实现

function deepCopy(obj) {
    if (typeof obj !== "object" || obj === null) {
        return obj;
}
    let copy = {};
    if (obj.constructor === Array) {
        copy = [];
    }
    //遍历被拷贝对象,如果第一层数据下还有子节点,则使用递归继续遍历拷贝
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            copy[key] = deepCopy(obj[key])
    }
}
    return copy
}
let x = {
a: 1,
b: {
c: 2,
d: 3
}
}
let y = deepCopy(x)
y.b.d = 4
console.log("修改y不影响x", x)
console.log("修改后y", y)
let m = [{
i: 1
},
{
k: 2,
l: 3
}
]
let n = deepCopy(m)
n[1].l = 4
console.log("修改m不影响n", x)
console.log("修改后m", y)

以上封装了一个名为deepCopy的深拷贝通用方法,constructor是用来验证被拷贝对象是否为数组的;关键点是使用for in来遍历对象;使用对象和数组分别进行了方法的验证:
4.jpg
保证了数据类型拷贝后的一致。

除了递归,还可以使用JSON.stringify和JSON.parse实现深拷贝:JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象


function deepCopy(obj1) {
    let _obj = JSON.stringify(obj1);
    let obj2 = JSON.parse(_obj);
    return obj2;
}
var a = [1, [1, 2], 3, 4];
var b = deepCopy(a);
b[1][0] = 2;
alert(a); // 1,1,2,3,4
alert(b); // 2,2,2,3,4

缺陷:它会抛弃对象的constructor,深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object;这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,也就是说,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON;

let obj1 = {
    fun:function(){
    alert(123);
    }
}
let obj2 = JSON.parse(JSON.stringify(obj1));
console.log(typeof obj1.fun); // function
console.log(typeof obj2.fun); // undefined

还可以借助第三方的插件和库来实现深拷贝,如jq库,热门的lodash函数库等等;

暂无评论