带你详细了解JS中的浅拷贝与深拷贝

738 阅读4分钟

前言

首先我们从字面意思来理解拷贝即复制(copy)的意思,在JS中它分为浅拷贝(shallow copy)深拷贝(deep copy)两种,在我们实际的项目开发过程中有时需要我们去实现拷贝,同时面试时可能也会问相关的问题。

数据类型

深入了解JS中的浅拷贝与深拷贝之前,我们来看一下JS中的数据类型。JS中数据类型分为两种:一种是基本数据类型(Number,String,Boolean,Null,Undefined,Symbol(es6新增)),这种数据类型存储在栈中;一种是引用类型(Array,Object,Function),这种数据类型则是地址存储在栈中,数据存储在堆内存中。看下图:

无标题.png

浅拷贝(shallow copy)

什么是浅拷贝?

在栈中开辟一个新的空间,存放拷贝的一份数据,基本数据类型的值会全部拷贝一遍,而引用类型则只是复制地址存放在新开辟的栈空间中,当原引用类型的数据改变时拷贝得到的对象也会改变,简单来说浅拷贝是拷贝得到的对象会受原对象的影响

实现浅拷贝的方法

1.引用类型的直接赋值

let a = { name: 'song' }
let b = a
a.name = 'jian'
console.log(b);

此时结果为{name = 'jian'},通过改变a对象,b对象也改变了,像这样也可以称之为浅拷贝。

2.Object.create()

let a = { name: song }
let b = Object.create(a)

3.arr.concat() 数组拼接

let arr = [1, '1', true, null, undefined, { n: 'as' }]
let newArr = arr.concat()
arr[0] = 2
arr[5].n = 'qw'
console.log(newArr);

结果为 [1, '1', true, null, undefined, { n: 'qw' }],我们可以看到此时基本数据类型拷贝的对象没有受原对象的影响,但是引用类型的拷贝还是受了影响;也就是说,基本数据类型深拷贝了,引用数据类型只是浅拷贝。像这样只要拷贝得不彻底,我们都称为浅拷贝。

4.arr.slice() 数组删除

let arr = [1, '1', true, null, undefined, { n: 'as' }]
let newArr = arr.slice()//从0砍到最后一位,返回出一个新的数组
arr[0] = 2
arr[5].n = 'qw'
console.log(newArr);

这个结果和arr.concat()方法一样,也是属于浅拷贝。

5.object.assign() 对象合并

let obj1 = {
    a: 1,
    b: {
        c: 2
    },
    sym: Symbol(1)
}

let obj2 = {}
Object.assign(obj2, obj1)
obj1.a = 3
obj1.b.c = 4
console.log(obj2);

代码运行结果为:

1.png 我们可以看到也是上面两种方法的同样问题,拷贝的引用类型依旧存在着访问共同堆的问题,也只是浅拷贝。

扩展运算符方式

语法为let obj2 = {...obj1}

此方法结果和object.assign()一样,都是属于浅拷贝。

手写浅拷贝函数

原理

用typeof方法判断实参是否为对象,创建一个空对象(数组也是对象),遍历原对象,把原对象里面的值一一赋值给空对象。(只考虑对象)

function shallowCopy(obj) {
    //只考虑对象,不是对象或为空就返回出去
    if (typeof obj !== 'object' || obj === null)
        return
        //创建一个空对象
    let newObj = obj instanceof Array ? [] : {}
    //遍历原对象,将原对象的值赋予空对象
    for (let key in obj) {
        newObj = obj[key]
    }
    //返回出拷贝好的对象
    return newObj
}

深拷贝

什么是深拷贝?

与浅拷贝相对应,它不仅会在栈中开辟一个空间存放数据,如果被拷贝对象有引用类型时,它也会在堆中开辟另一个空间来存放引用类型的数据,这样两个对象指向的是不同的地址;深拷贝得到的对象就不会受原对象的影响

用JSON.parse(JSON.stringify(obj))进行深拷贝

  • JSON.stringfy()将对象序列化成json对象(转换成JSON字符串)
  • JSON.parse()反序列化———将json对象反序列化成js对象(解析成对象)
let obj = { name: '高富帅', age: 20 }
let newObj = JSON.parse(JSON.stringify(obj))
let arr = [1, '1', null, true, undefined]
let newArr = JSON.parse(JSON.stringify(arr))
arr[1] = 'a'
obj.name = 'song'
console.log(newObj);
console.log(newArr);

结果为:

2.png 从结果我们可以看出通过JSON.parse(JSON.stringify(obj))方法进行的拷贝原对象的改变无法影响拷贝出的对象,这就是深拷贝。

注意:此方法不能拷贝undefined,函数,无法处理循环引用的情况,不能拷贝Symbol类型。

手写深拷贝函数(面试考点)

原理

用typeof方法判断实参是否为对象,创建一个空对象(数组也是对象),用for key in obj遍历原对象,obj[key] 是基本类型才直接赋值,如果是引用类型,就需要循环递归一层一层找到基本类型赋值。

function deepCopy(obj) {
    if (typeof obj !== 'object' || obj === null) return
    let newObj = obj instanceof Array ? [] : {}
    //循环遍历对象
    for (let key in obj) {
        // obj[key] 是原始类型才直接赋值,是引用类型就循环递归
        if (typeof obj[key] === 'object' && obj[key] !== null) {
            // 递归实现多层引用类型的赋值
            newObj[key] = deepCopy(obj[key])
        } else {
            //直接赋值
            newObj[key] = obj[key]
        }

    }
    return newObj
}

小结

我们在开发项目的过程中,常常需要我们去分清楚浅拷贝与深拷贝。 如果你和同事一起开发一个项目,你们都需要用到一个公共的对象,方法之一就是深拷贝一个对象。最后感谢各位的观看,码字不易,点个免费的赞吧~