js中的深拷贝和浅拷贝

178 阅读3分钟

如何区分深拷贝和浅拷贝

简单来说:B赋值A,当改变B中的某些值时,A不受其影响,就是深拷贝,如果变了就是浅拷贝;

先上几个概念:

js的数据类型

js的数据类型分两种:

第一种: 基本数据类型: Number、String、Boolean、undefined、null、symbol(ES6)、(以及ES10中的)Biginit;

第二种:引用数据类型: Object;

接下来聊聊他们各自类型数据的存储方式,这里会讲到栈、堆内存的概念。

1.基本数据类型的存储方式是属性的名和值都是存储在栈内存中。

例如: let a=1;

栈内存
name value
a 1

let b = a;

栈内存
name value
a 1
b 1

变量b复制了变量a的时候,b会在栈内存中被当成一条新数据被存储,他们两个是独立且没有关系的;

2.引用数据类型的存储方式是把属性名和指向堆内存中值的地址存储在栈内存中,值是放在堆内存中

例如:let c = [0,1,2];

栈内存 堆内存
name value value
c 地址1(指向堆内存的值) → [0,1,2]

let d = c;

栈内存 堆内存
name value value
c
d
地址1(指向堆内存的值)
地址1(指向堆内存的值)
[0,1,2]

当改变d[0] = 1

栈内存 堆内存
name value value
c
d
地址1(指向堆内存的值)
地址1(指向堆内存的值)
[ 1 ,1,2]

c[0]的值也会被改变成1,因为他们的地址指向的是同一个值;

简单实现深拷贝

1.通过递归的方式复制原对象给一个新对象,并且返回新对象;(ps:只是一个简单的展示,并非最佳实践)。

function deepCopy(obj) {
    let objClone = Array.isArray(obj) ? [] : {};
    if (obj && typeof obj === 'object') {
        for (key in obj) {
            if (obj.hasOwnProperty(key)) {
                if (obj[key] && typeof obj[key] === 'object') {
                    objClone[key] = deepCopy(obj[key]);
                } else {
                    objClone[key] = obj[key];
                }
            }
        }
    }
  	return objClone;
}

let a = [0,1,2];
let b = deepCopy(a);
b[0] = 1;
console.log(a); // [0,1,2]
console.log(b); // [1,1,2]

2.通过JSON.stringify() 和 JSON.parse() 来简单实现深拷贝;

function deepCopy(obj) {
    let objClone = JSON.stringify(obj);
    return JSON.parse(objClone);
}
let a = [0,1,2];
let b = deepCopy(a);
b[0] = 1;
console.log(a); // [0,1,2]
console.log(b); // [1,1,2]

3.接下来再说说数组中的slice()和cocat(),看他们实现的是深拷贝还是浅拷贝;

// slice 截取数组 返回一个新数组;
let arr1 = [0,1,2];
let arr2 = arr1.slice();
console.log(arr1 === arr2); // false
arr2[0] = 1;
console.log(arr1); // [0,1,2]
console.log(arr2); // [1,1,2];

// cocat 合并数组 返回一个新数组;
let arr3 = [4,5,6];
let arr4 = arr1.concat();
console.log(arr3 === arr4); // false
arr3[0] = 5;
console.log(arr3); // [4,5,6]
console.log(arr4); // [5,5,6];

上面例子中貌似都实现了深拷贝,其实并没有;

let arr = [0,1,2,[3,4]];
let arr2 = arr.slice();
let arr4 = arr.concat();

arr2[3][0] = 4;
console.log(arr); // [0,1,2,[4,4]]
console.log(arr2); // [0,1,2,[4,4]]
arr4[3][0] = 5;
console.log(arr); // [0,1,2,[5,4]]
console.log(arr2); // [0,1,2,[5,4]]
console.log(arr4); // [0,1,2,[5,4]]

可以看出slice()和concat()感觉实现了深拷贝,但是当数组有更深一级的时候,它们并没有完全拷贝,只是复制了引用地址。由此可见slice()和concat()只能拷贝一层而已,并不能实现真正意义上的深拷贝;

总结:如果是简单的基本类型数据复制,就是一份是一份,但是对于引用类型的值来说,必须要用深拷贝的方法进行拷贝,避免引用地址相同造成数据同时改变的问题;