深拷贝?浅拷贝?

187 阅读4分钟

想必很多人在学习 coding 的过程中都听说过深浅拷贝吧,但是哪些属于深拷贝?哪些又属于浅拷贝?是不是又理不清了呢。接下来,咱们就来聊一聊深拷贝与浅拷贝。

浅拷贝

什么是浅拷贝

简单来说,浅拷贝就是指原对象的属性变化会影响拷贝对象的属性值变化,且变化的属性一定为引用类型。浅拷贝的原理是由于新拷贝对象只是拷贝了原对象属性值的引用地址,所以新旧对象的值指向的是同一块地址,改变该地址的值,新旧对象的属性值都会发生改变。

我们可以这样理解——浅拷贝只拷贝的对象的第一层,也就是属性为原始类型的直接拷贝值;属性为引用类型的也直接拷贝引用地址,并不会深层次的对引用类型进行拷贝,故而称作浅拷贝。

哪些方法实现浅拷贝?

(1)Object.assign()

let a = {
    age: 18,
    like:{
        n1:'reading',
        n2:'running'
    }
}

let b = Object.assign({},a)     
a.age = 20      // 深拷贝
a.like.n1 = 'coding'      // 浅拷贝

console.log(b);  // { age: 18, like: { n1: 'coding', n2: 'running' } }

// Object.assign({},obj), obj中的原始类型的属性会被深拷贝,引用类型的属性会被浅拷贝(直接拷贝引用地址)

(2)解构对象{...obj}、解构数组[...arr]

// 结构对象
let a = {
    age:18,
    like:{
        n1:'reading'
    }
}
let b = {...a}
a.age = 20      // 深拷贝
a.like.n1 = 'coding'       // 浅拷贝

console.log(b);    // { age: 18, like: { n1: 'coding' } }


// 解构数组
let arr = [{old:'old'},['old'],'1']
let newArr = [...arr]

arr[0].old = 'new'
arr[1][0] = 'new'
arr[3] = 2

console.log(newArr);       // [ { old: 'new' }, [ 'new' ], '1' ]


// 和 Object.assign() 类似, obj中的原始类型的属性会被深拷贝,引用类型的属性会被浅拷贝(直接拷贝引用地址)

(3)数组:arr.concat()

let arr = [{old:'old'},['old'],'1']
let newArr = arr.concat()

arr[0].old = 'new'
arr[1][0] = 'new'
arr[3] = 2

console.log(newArr);       // [ { old: 'new' }, [ 'new' ], '1' ]
// 原始类型的属性会被深拷贝,引用类型的属性会被浅拷贝

(4)数组:arr.slice()

let arr = [{old:'old'},['old'],'1']
let newArr = arr.slice()

arr[0].old = 'new'
arr[1][0] = 'new'
arr[3] = 2

console.log(newArr);       // [ { old: 'new' }, [ 'new' ], '1' ]
// 原始类型的属性会被深拷贝,引用类型的属性会被浅拷贝

手写实现浅拷贝

实现思想:

(1)判断原对象是否是引用类型 (2)判断原对象是对象还是数组 (3)遍历原对象中的属性或下标,将内容拷贝给新对象

// 浅拷贝实现
function shallowCopy(obj){
    if(typeof obj !== 'object') return 

    var newObj = obj instanceof Array ? [] : {}    // 判断原对象是对象还是数组,以确定拷贝对象的类型

    for (let key in obj){
        if(obj.hasOwnProperty(key)){    // 判断这个属性是否是显式属性(防止取到原型上的属性)
            newObj[key] = obj[key]
        }
    }
    return newObj
}

// 验证
let a = {
    age:1,
    like:{
        n1:'reading'
    }
}
let b = shallowCopy(a)
a.age = 5
a.like.n1='coding'
console.log(b);    // { age: 1, like: { n1: 'coding' } }

深拷贝

什么是深拷贝

深拷贝相当于由原对象复刻出来一份一模一样的数据赋给拷贝对象,并且当改变原对象的属性值时,拷贝对象不受影响。这是因为当原对象的属性为引用类型时,深拷贝会取顺着引用类型的引用地址,去堆里面找到相应的数据,如果值还是引用类型,则继续往下找,这样深层次的拷贝叫做深拷贝。

哪些方法实现深拷贝?

(1)JSON.parse(JSON.stringify(obj))

let a = {
    age:18,
    like:{
        n1:'reading'
    }
}
let b = JSON.parse(JSON.stringify(a))
a.age = 20      // 深拷贝
a.like.n1 = 'coding'       // 浅拷贝

console.log(b);    // { age: 18, like: { n1: 'reading' } }

但是这个方法存在一些缺陷

  1. 会忽略 undefined、 Symbol 和 函数三种类型的属性
let obj = {
    a:1,
    b:{
        c:2,
        d:3
    },
    e:undefined,
    f:Symbol('hello'),
    g: function(){}
}
let newobj = JSON.parse(JSON.stringify(obj))
console.log(newobj);   // { a: 1, b: { c: 2, d: 3 } }
  1. 不能处理循环引用的对象
let obj = {
    a:1,
    b:{
        c:2,
        d:3
    }
}
obj.c = obj.b
obj.b.c = obj.c
obj.b.d = obj.b

let newobj = JSON.parse(JSON.stringify(obj))
console.log(newobj);    // 报错

手写实现深拷贝

实现思想:

(1)判断原对象是否为引用类型 (2)判断原对象是对象还是数组 (3)判断属性是否为原始类型,若为引用类型,则递归

// 深拷贝实现
function deepCopy(obj) {
    if(typeof obj !== 'object') return
    let newObj = obj instanceof Array ? [] : {}
    
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key]
        }
    }
    return newObj
}

// 验证
let obj = {
    a:1,
    b:{
        c:2,
        d:3
    },
    c:function(){}
}

let b = deepCopy(obj)
obj.a = 2
obj.b.c = 3

console.log(b);     // { a: 1, b: { c: 2, d: 3 }, c: [Function: c] }