浅拷贝&深拷贝

201 阅读3分钟

更多文章

前言

日常开发经常会遇到复用一组数据的场景,这里会遇到对原数据是否有影响的问题,从而引出浅拷贝深拷贝两个概念

浅拷贝

只对目标对象第一层基础类型赋值,引用类型赋址,所谓赋址的址就是在堆中地址

实现浅拷贝

这里使用Object.keys实现,和for in的区别是for in不仅会拿到对象自身的属性,也会拿到继承的属性

const type = (data) => Object.prototype.toString.call(data)
function qCopy(data) {
    // 这里可以添加data非对象的判断。。。。。。
    let newObj = type(data) == '[object Array]' ? [] : {};
    Object.keys(data).forEach(item=>newObj[item] = data[item])
    return newObj
}
var obj1 = { a: 1, b: { b1: 1 } }
var obj2 = qCopy(obj1)
obj2.a = 2
obj2.b.b1 = 2
console.log('obj2---', obj2) // { a: 2, b: { b1: 2 } }
console.log('obj1---', obj1) // { a: 1, b: { b1: 2 } }

var arr1 = [1, 2, { a: 1 }]
var arr2 = qCopy(arr1)
arr2[0] = 2
arr2[2].a = 2
console.log('arr2---', arr2) // [2, 2, { a: 2 }]
console.log('arr1---', arr1) // [1, 2, { a: 2 }]

常见浅拷贝

  • Object.assign()
  • [...arr]、{...obj}
  • [].concat(arr)
  • Array.prototype.slice()
// Object.assign()
var obj1 = { a: 1, b: { b1: 1 } }
var obj2 = Object.assign({}, obj1)
obj2.a = 2
obj2.b.b1 = 2
console.log('obj1', obj1) // { a: 1, b: { b1: 2 } }
console.log('obj2', obj2) // { a: 2, b: { b1: 2 } }
// { ...obj }
var obj2 = { ...obj1 }
console.log('obj1', obj1) // { a: 1, b: { b1: 2 } }
console.log('obj2', obj2) // { a: 2, b: { b1: 2 } }

// [].concat(arr)
var arr1 = [1, 2, {a: 1} ]
var arr2 = [].concat(arr1)
arr2[0] = 2
arr2[2].a = 2
console.log('arr1', arr1) // [1, 2, {a: 2} ]
console.log('arr2', arr2) // [2, 2, {a: 2} ]
// slice()
var arr2 = [].concat(arr1)
console.log('arr1', arr1) // [1, 2, {a: 2} ]
console.log('arr2', arr2) // [2, 2, {a: 2} ]
// [...arr]
var arr2 = [...arr1]
console.log('arr1', arr1) // [1, 2, {a: 2} ]
console.log('arr2', arr2) // [2, 2, {a: 2} ]

深拷贝

对目标对象中基础类型赋值,所有引用类型开辟新的地址

JSON.parse(JSON.stringify())

其实日常JSON.parse(JSON.stringify())可以满足绝大部分的需求,接下来我们就来看一下它的问题

function Test() {}
var obj1 = {
    a: 1,
    b: '2',
    c: { c1: 1 },
    d: function() {},
    e: [1, 2],
    f: undefined,
    g: null,
    h: true,
    i: new Date(),
    j: /1/,
    k: NaN,
    l: ()=>123,
    m: new Test(),
    n: Symbol("KK"),
    o: new Error(),
    p: Infinity,
    x: {}
}
// obj1.n = obj1
var obj2 = JSON.parse(JSON.stringify(obj1))
console.log(obj2)
console.log(obj2.m.__proto__)
console.log(obj1.m.__proto__)
// 打印结果
// obj2属性如下:
// a: 1
// b: "2"
// c: {c1: 1}
// e: [1, 2]
// g: null
// h: true
// i: "2020-12-25T07:56:52.734Z"
// j: {}
// k: null
// m: {}
// o: {}
// p: null
// x: {}
// proto2 { constructor: f Object() }
// proto1 { constructor: f Test(), __proto__: Object  }

根据以上打印结果可以看出JSON.parse(JSON.stringify())会造成如下问题:

  1. 普通函数、箭头函数、Symbol、undefined数据丢失
  2. Date类型转成了字符串
  3. 正则、Error类型变成了空对象
  4. NaN、Infinity变成了null
  5. 构造函数实例变为普通对象,constructor变成函数Object

继续看问题六:

obj1.x.x1 = obj1.c
var obj2 = JSON.parse(JSON.stringify(obj1))
console.log(obj1.x.x1 === obj1.c) // true
console.log(obj2.x.x1 === obj2.c) // false
// 同级引用创造了新对象
  1. 同级引用创造了新的对象

继续问题七:

obj1.q = obj1
var obj2 = JSON.parse(JSON.stringify(obj1))
// Uncaught TypeError: Converting circular structure to JSON
// 导致栈溢出
  1. 父级循环引用导致栈溢出

这是目前发现的问题欢迎补充

lodash cloneDeep

<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>

obj1.q = obj1
obj1.x.x1 = obj1.c
var obj3 = _.cloneDeep(obj1)
console.log(obj3) // 正常打印,且数据结构正常
console.log(obj1.x.x1 === obj1.c) // true
console.log(obj3.x.x1 === obj3.c) // true

Jquery extend

话说有3年没接触过Jquery

var obj4 = $.extend({}, obj1)
console.log(obj4) // 正常打印,且数据结构正常
console.log(obj1.x.x1 === obj1.c) // true
console.log(obj4.x.x1 === obj4.c) // true

实现深拷贝

const type = (data) => Object.prototype.toString.call(data)
let map = new WeakMap()
function dpCopy(data) {
    // 区分[]和{}
    let newData = type(data) == '[object Array]' ? [] : {};
    // 通过WeakMap存储新老数据,解决同级和父级引用问题
    map.set(data, newData)
    Object.keys(data).map(item=>{
        if(type(data[item]) == ('[object Object]' ||  '[object Array]')) {
            if(map.get(data[item])) { // 存在直接赋值
                newData[item] = map.get(data[item])
            } else {
                // 对象构造函数非Object时继承构造函数
                if (data[item].__proto__.constructor === Object) {
                    newData[item] = dpCopy(data[item])
                } else {
                    newData[item] = dpCopy(data[item])
                    newData[item].__proto__ = data[item].__proto__
                }
            }
        } else {
            newData[item] = data[item]
        }
    })
    return newData
}
function Test() {}
var obj1 = {
    a: 1,
    b: '2',
    c: { c1: 1 },
    d: function() {},
    e: [1, 2],
    f: undefined,
    g: null,
    h: true,
    i: new Date(),
    j: /1/,
    k: NaN,
    l: ()=>123,
    m: new Test(),
    n: Symbol("KK"),
    o: new Error(),
    p: Infinity,
    x: {}
}
obj1.q = obj1
obj1.x.x1 = obj1.c
var obj2 = dpCopy(obj1)
console.log(obj2) // 正常打印,且数据结构正常
console.log(obj1.x.x1 === obj1.c) // true
console.log(obj2.x.x1 === obj2.c) // true
console.log(obj2.m.__proto__ === Test.prototype) // true

网上看到了while比递归更快,有兴趣的试试

结语

没事走两步!!!