浅谈拷贝

239 阅读3分钟

数据存储

数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和对象数据类型(Object,Function)

基本数据类型的特点:数据直接存储在

引用数据类型的特点:栈上存储的是对象的地址,实际数据存储在

image.png

概念

  • 赋值:

    • 对于基本数据类型,赋值,赋值之后两个变量互不影响 img
    • 对于引用数据类型,赋址,他们都会指向同一地址 img
  • 浅拷贝:

    • 对于基本数据类型而言,拷贝的是
    • 对于引用数据类型而言,拷贝的是对象的地址,结果是两个对象都会指向同一个地址
  • 深拷贝:深拷贝会为对象开辟一个新的空间,来存储对象拷贝的值,因为拷贝的不是同一地址,所以不会相互影响各自的属性

下面是关于对象的深浅拷贝示意图

image.png

如何实现

浅拷贝

1. Object.assign

let a = {
    age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1

2. 展开运算符(…)

let a = {
    age: 1
}
let b = {...a}
a.age = 2
console.log(b.age) // 1

深拷贝

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

let a = {
    age: 1,
    jobs: {
        first: 'FE'
    }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE

缺点:

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象
  • 不能正确处理new Date()
  • 不能处理正则
证明
  • undefinedsymbol 和函数
let obj = {
    name: 'sumuy',
    a: undefined,
    b: Symbol('Sumuyzzz'),
    c: function() {}
}
console.log(obj);
// {
//  name: "sumuy", 
//  a: undefined, 
//  b: Symbol(Sumuyzzz), 
//  c: ƒ ()
// }
let b = JSON.parse(JSON.stringify(obj));
console.log(b);
//{name:"sumuy"}
  • 循环引用情况下,会报错。
let obj = {
    a: 1,
    b: {
        c: 2,
        d: 3
    }
}
obj.a = obj.b;
obj.b.c = obj.a;
let b = JSON.parse(JSON.stringify(obj));
  • new Date 情况下,转换结果不正确。
new Date();
JSON.stringify(new Date());

手写深拷贝

基础版本

function clone(target) {
    let cloneTarget = {};
    for (const key in target) {
        cloneTarget[key] = target[key];
    }
    return cloneTarget;
};
​
​

创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性依次添加到新对象上,返回。

考虑引用类型和原始类型

function clone(target) {
    if (typeof target === 'object') {
        let cloneTarget = {};
        for (const key in target) {
            cloneTarget[key] = clone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
};
const target = {
    field1: 1,
    field2: undefined,
    field3: 'Sumuyzzz',
    field4: Symbol('sumuyzzz'),
    field5: {
        child: 'child',
        child2: {
            child2: 'child2'
        }
    },
    field6:function(){},
};
​
let result = clone(target)
console.log(result)
​

执行结果:

image.png

考虑数组版本

function clone(target) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {}
        for (const key in target) {
            cloneTarget[key] = target[key]
        }
        return cloneTarget
    } else {
        return target
    }
}
const target = {
    field1: 1,
    field2: undefined,
    field3: 'Sumuyzzz',
    field4: Symbol('sumuyzzz'),
    field5: {
        child: 'child',
        child2: {
            child2: 'child2'
        }
    },
    field6:function(){},
    field7: [1, 2, 4, 8]
};

let result = clone(target)
console.log(result)

执行结果

image.png

考虑循环引用

解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。

function clone(target, map = new Map()) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {}
        if (map.get(target)) {
            return map.get(target)
        }
        map.set(target, cloneTarget)
        for (const key in target) {
            cloneTarget[key] = clone(target[key], map)
        }
        return cloneTarget
    }
    return target
}

const target = {
    field1: 1,
    field2: undefined,
    field3: 'Sumuyzzz',
    field4: Symbol('sumuyzzz'),
    field5: {
        child: 'child',
        child2: {
            child2: 'child2'
        }
    },
    field6: function () {},
    field7: [1, 2, 4, 8]
};
target.target = target;
const result = clone(target);

console.log(result);

image.png

总结

赋值:当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据

let obj ={
    a:1,
    b:{
        c:2,
        d:3,
    }
}
let newObj = obj
​
console.log(newObj) //{a:1,b{...}}
console.log(obj === newObj) //true

浅拷贝:创建一个新的对象,如果属性的基本类型,拷贝的就是,如果是引用类型,拷贝的就是内存地址

let obj ={
    a:1,
    b:{
        c:2,
        d:3,
    }
}
let newObj = {...obj}
​
console.log(newObj) //{a:1,b{...}}
console.log(obj === newObj) //false
console.log(obj.b === newObj.b) //true

深拷贝:深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

let obj ={
    a:1,
    b:{
        c:2,
        d:3,
    }
}
​
let newObj = JSON.parse(JSON.stringify(obj))
​
console.log(newObj) //{a:1,b{...}}
console.log(obj === newObj) //false
console.log(obj.b. === newObj.b) //false