js浅拷贝与深拷贝

125 阅读4分钟

浅拷贝与深拷贝

1. js的数据类型有两种:

基本数据类型:number、string、boolean、undefined、null、symbol、bigInt;

引用数据类型:Object。

2. 数据类型的存储方式:

基本数据类型:基本类型的值存储在栈内存中,栈内存有固定大小;

引用数据类型:引用类型的值是对象,该对象保存在堆内存中,而堆内存的指针保存在栈内存中。

3. 拷贝效果:

基本数据类型:拷贝得到副本的值与原变量的值一样,操作其中一个对象不会对另一个对象产生影响;

引用数据类型:

浅拷贝:拷贝得到副本的值是原变量在栈内存的地址,副本与原变量同时指向同一个地址,操作任意一个对象都会对另一个对象产生影响;

深拷贝:拷贝得到的副本在堆中重新分配内存,与原变量的值相同,但是栈中的地址不同,两个变量间互不影响。

4. 浅拷贝
Array.prototype.slice()
//例1:
let a=[1,2,3];
let b=a.slice();
console.log(b);//[1,2,3]

a.push(4)
console.log(b);//[1,2,3]b的值没有随着a的改变而变化

//例2:
let a=[1,2,3,[4,5]];
let b=a.slice();
console.log(b);//[1,2,3,[4,5]]

a[3].push(6);
console.log(b); //[1,2,3,[4,5,6]],b的值随着a的改变而变化了
Array.prototype.concat()
//例1:
let a = [1, 2, 3];
let b = a.concat();
console.log(b); //[1,2,3]

a.push(4);
console.log(b); //[1,2,3]b的值没有随着a的改变而变化

//例1:
let a = [1, 2, 3,[4,5]];
let b = a.concat();
console.log(b); //[1, 2, 3,[4,5]]
a[3].push(6);
console.log(b); //[1, 2, 3,[4,5,6]],b的值随着a的改变而变化了
Array.from()
//例1:
let a = [1, 2, 3];
let b = Array.from(a);
console.log(b); //[1,2,3]

a.push(4);
console.log(b);[1,2,3]b的值没有随着a的改变而变化

//例2:
let a = [1, 2, 3,[4,5]];
let b = Array.from(a);
console.log(b); //[1, 2, 3,[4,5]]

a[3].push(6);
console.log(b); //[1, 2, 3,[4,5,6]],b的值随着a的改变而变化了

Object.assign()
// Object.assign()中的花括号叫目标对象,后面的参数是源对象。对象属性合并是指:将源对象里面的属性添加到目标对象中去,若两者的属性名有冲突,后面的将会覆盖前面的。
//例1:
let a = { name: 'yingzi' }
let b = Object.assign({}, a);
console.log(b); //{name: 'yingzi'}

a.age = 18;
console.log(b); //{name: 'yingzi'},b的值没有随着a的改变而变化

//例2:
let a = { name: 'yingzi', nationality: {country:'中国',city:'广州'}}
let b = Object.assign({}, a);
console.log(b); //{ name: 'yingzi', nationality: {country:'中国',city:'广州'}}

a.nationality.city = "上海"
console.log(b); //{ name: 'yingzi', nationality: {country:'中国',city:'上海'}},b的值随着a的改变而变化了

扩展运算符
//例1:
let a = [1, 2, 3];
let b = [...a];
console.log(b); //[1, 2, 3]

a.push(4);
console.log(b); //[1, 2, 3]b的值没有随着a的改变而变化

//例2:
let a = [1, 2, 3,[4,5]];
let b = [...a];
console.log(b); //[1, 2, 3,[4,5]]

a[3].push(6);
console.log(b); //[1, 2, 3,[4,5,6]],b的值随着a的改变而变化了

5. 深拷贝

JSON.stringify()、JSON.parse()
//例1:
let a = { name: 'yingzi' }
let b = JSON.parse(JSON.stringify(a));
console.log(b); //{name: 'yingzi'}

a.age = 18;
console.log(b); //{name: 'yingzi'},b的值没有随着a的改变而变化

//例2:
let a = { name: 'yingzi', nationality: {country:'中国',city:'广州'}}
let b = JSON.parse(JSON.stringify(a));
console.log(b); //{ name: 'yingzi', nationality: {country:'中国',city:'广州'}}

a.nationality.city = "上海"
console.log(a); ////{ name: 'yingzi', nationality: {country:'中国',city:'上海'}}
console.log(b); //{ name: 'yingzi', nationality: {country:'中国',city:'广州'}},b的值没有随着a的改变而变化

JSON方法的缺点:不可以拷贝undefined、function、RegExp等类型。

递归遍历
// 递归函数:
function deepCloneRecur(obj) { 
    // 判断拷贝对象的类型,从而给新对象赋相应的类型
    var newObj = Array.isArray(obj) ? [] : {};
    // 判断当前拷贝的对象不为空,且类型是object才进行递归进行深度遍历
    if (obj && typeof obj === 'object') { 
        for (var key in obj) { 
            if (obj.hasOwnProperty(key)) { //自身的属性才递归赋值
                if (obj[key] && typeof obj[key] === 'object') {
                    newObj[key] = deepCloneRecur(obj[key]);
                } else { 
                    newObj[key] = obj[key];
                }
            }
        }
    }
    return newObj
}

//例1:
let a = { name: 'yingzi', nationality: { country: '中国', city: '广州' } };
let b = deepCloneRecur(a);
console.log(b); //{ name: 'yingzi', nationality: { country: '中国', city: '广州' } }

//例2a.nationality.city = "上海";
console.log(a); //{ name: 'yingzi', nationality: { country: '中国', city: '上海' } }
console.log(b); //{ name: 'yingzi', nationality: { country: '中国', city: '广州' } },b的值没有随着a的改变而变化

因为for in 循环会枚举所有可枚举属性,包含原型上的,所以递归函数中要判断当前遍历的属性是否属于自己身上的属性。