likes
comments
collection
share

手写浅拷贝与深拷贝方法,由易到深入

作者站长头像
站长
· 阅读数 4

前言

在JavaScript中,深拷贝和浅拷贝是常用的对象拷贝(复制)的方式。深拷贝创建一个新对象,并将原始对象的所有属性和嵌套对象都复制到新对象中。与之相反,浅拷贝只复制原始对象的属性的引用而不是属性的值。下面我将详细介绍深拷贝和浅拷贝,以及实现手写一个浅拷贝和深拷贝的方法。

浅拷贝

浅拷贝是指创建一个新对象,新对象的属性与原始对象相同,但属性引用的对象还是指向原始对象引用的对象。换句话说,浅拷贝只复制了对象的引用,而不是对象本身。当原始对象的属性是基本数据类型时,浅拷贝对属性值进行拷贝。但当原始对象的属性是引用类型时,浅拷贝只拷贝了引用,并没有创建新的对象。 通过对浅拷贝概念的理解,要实现其原理,无非就是创建一个新对象,然后通过遍历将原始对象的属性全部拿到新对象里面。所以问题就是这个拿该如何实现呢?那我们先来了解一下for in方法。

for in

for in方法既可以用来遍历数组,也可以遍历对象。 当for in遍历数组时,这里形参key相当于数组的每一项下标,arr[key]就是arr数组中的每一项值。

const arr = ['1', '2', '3', '4', '5', '6']

for (const key in arr) {
   console.log(key);  //依次打印数组每一项下标0,1,2,3,4,5
}

for(const key in arr) {
    console.log(arr[key]); //依次打印数组每一项1,2,3,4,5,6
}

arr['like'] = 'writing' //给arr数组添加一个下标为like,值为writing

for (const key in arr) {
    console.log(key);  //依次打印数组每一项下标0,1,2,3,4,5,like
 }

for(const key in arr) {
    console.log(arr[key]); //依次打印数组每一项1,2,3,4,5,6,writing
}

for in 用来遍历对象,就和遍历数组一样的,这里形参key为对象obj的每一项属性名,obj[key]obj对象的每一项属性名的值,特别注意obj.key不是获取obj每一项的值,这里的key会被当成obj的一个属性,而不是变量,所以obj.key相当于获取obj上一个名为key的属性的值。

const obj = {
    name:'小米',
    age:18,
    like:{
        sport:'running'
    }
}

//给obj对象添加一个新的属性sex,值为'男'
obj['sex'] = '男'// 等价于obj.sex = '男'

for (const key in obj) {
    console.log(key); //依次打印name age like sex,即对象的各属性名
}

for (const key in obj) {
    console.log(obj.key); //依次打印undefined undefined undefined undefined
                          //注:不能这样遍历对象的值,key会被当成是obj的一个属性
}

for (const key in obj) {
    console.log(obj[key]); //依次打印 小米 18 { sport: 'running' } 男,遍历对象每一项
                          //就是和遍历数组一样的,对象名[属性名]
}

上面的对象遍历相信大家已经完全理解了,那如果我创建的对象隐式继承了一些属性,那使用for in方法来遍历结果还是一样的吗?我们一起来看一看:

const obj = {
    name:'小米',
    age:18,
    like:{
        sport:'running'
    }
}
//创建obj2空对象,隐式继承obj对象上的属性
const obj2 = Object.create(obj) 

//给obj2添加一个属性sex,值为'男'
obj2.sex = '男' 
console.log(obj2) // { sex: '男' } 

for(const item in obj2) {
    console.log(item)  //依次打印sex name age like
                       //将obj2隐式原型上的属性也遍历出来了
}

可以看到使用for in方法遍历对象,会将其隐式原型上的属性也遍历到。很显然,这种结果并不是我们想要的,所以我们该如何解决这种情况呢?我们继续看代码:

const obj = {
    name:'小米',
    age:18,
    like:{
        sport:'running'
    }
}

const obj2 = Object.create(obj) //创建obj2空对象,隐式继承obj对象上的属性
obj2.sex = '男' //给obj2添加一个属性sex,值为'男'
console.log(obj2) // { sex: '男' } 

for(const item in obj2) {
    //使用data.hasOwnProperty()方法,用来判断对象上的属性是不是隐式原型上的属性
    if(obj2.hasOwnProperty(item))  //是隐式原型上的属性,返回false,不是返回true
    console.log(item)  //只打印sex 
}

for in方法里面通过data.hasOwnProperty()方法来判断,遍历的属性是不是该对象的隐式原型上的属性,再加一个if判断就解决了for in会把对象隐式原型上的属性也遍历出来的问题啦~ 看到这里,相信大家已经对for in方法已经完全掌握啦!那接下来,我们实现手写浅拷贝的原理就非常简单啦~

手写浅拷贝方法

开始我们提到了,要实现浅拷贝,无非就是创建一个新对象,然后通过遍历将原始对象的属性全部拿到新对象里面。那我们通过for in方法去遍历原始对象的每一项,将其值赋值给新对象是不是就可以实现啦~看看代码:

//浅拷贝的实现原理
const obj = {
    name:'啊纬',
    age:18,
    like:{
        type:'coding'
    }
}

function shalldowCopy(obj){
    const objCopy = {}
    for(const key in obj){
        if(obj.hasOwnProperty(key)){  //不需要obj隐式原型上的属性
            //每次给新对象添加原对象的一个属性和属性值
            objCopy[key] = obj[key]
        }
    }
    return objCopy
}

const newobj = shalldowCopy(obj)
console.log(newobj) //{ name: '啊纬', age: 18, like: { type: 'coding' } }

到这里浅拷贝的核心代码其实就实现啦,当然还要添加一些细节,要对参数进行判断,上面的参数我们只考虑了它是对象,如果不是对象或者是null(null的类型判断是对象) ,我们就得直接返回。当然,数组也是对象,所以我们也要判断对象是不是数组,如果是数组,则将objCopy创建为数组,否则,创建为对象。那我们再更新一下代码:

//浅拷贝的实现原理
const obj = {
    name:'啊纬',
    age:18,
    like:{
        type:'coding'
    }
}
const arr = ['a', {n:1}, 1, undefined , null]

function shalldowCopy(obj){
 if(typeof obj !== 'object' || obj == null) return //不是对象或者是null直接返回
    const objCopy = obj instanceof Array ? [] : {} //判断obj是数组还是对象
    for(const key in obj){
        if(obj.hasOwnProperty(key)){  //不需要obj隐式原型上的属性
            //每次给新对象添加原对象的一个属性和属性值
            objCopy[key] = obj[key]
        }
    }
    return objCopy
}

console.log(shalldowCopy(obj)) //{ name: '啊纬', age: 18, like: { type: 'coding' } }
console.log(shalldowCopy(arr)) //[ 'a', { n: 1 }, 1, undefined, null ]

好~到这里我们就成功实现手写了一个浅拷贝的方法啦!是不是感觉很简单呢😁,下面我们再来看看深拷贝怎么实现叭。

深拷贝

相比之下,深拷贝是指创建一个新对象,并将原始对象的所有属性都复制到新对象中,不论这些属性是基本数据类型还是引用类型。深拷贝不仅复制了对象的引用,还创建了与原始对象属性引用的对象相同的新对象。因此,深拷贝得到的是一个全新的独立对象。

手写深拷贝方法

其实,在我们实现浅拷贝方法的时候就可以发现,我们给新对象赋值的时候,当原始对象的属性是基本数据类型时,浅拷贝对属性值进行赋值拷贝,但当原始对象的属性是引用类型时,浅拷贝赋值只拷贝了引用,并没有创建新的对象。所以,这也是为什么通过浅拷贝出来的新对象,改变原始对象的引用类型数据,新对象的引用类型数据也会跟着改变的原因所在了。 那要实现深拷贝,是不是只要当原始对象的属性是引用类型时,我们能得到一个和它有相同属性的新对象就可以啦~那我们就可以这样想了,我们实现浅拷贝的时候,就是将原对象所有属性都赋值到了新对象,然后将新对象返回出来了,欸,这个返回的对象是不是就是一个新对象,那我们是不是可以这样,在将原始对象的属性拷贝到新对象的时候,判断它是不是引用类型,如果不是,则直接添加到新对象里面,如果是,那就将该引用类型的属性作为参数再次调用拷贝方法,待它被拷贝完成后,是不是就会返回了一个新的对象。这样是不是就实现了将原始对象的所有属性都复制到新对象中,并且当原始对象的属性是引用类型时,创建了一个新的对象拷贝了里面的属性值。那我们来看看代码如何实现:

//手动实现深拷贝
const obj = {
    name:'啊纬',
    age:18,
    like:{
        type:'coding'
    }
}

function deepCopy(obj) {
    const objCopy = {}
    for (const key in obj) {
       if (obj.hasOwnProperty(key)) {
        if(obj[key] instanceof Object){ //判断obj中的属性是不是引用类型
            objCopy[key] = deepCopy(obj[key]) //是引用类型,将该属性作为参数再次调用拷贝方法
                                              //返回一个新对象后再赋值回objCopy[key]
        }  
        else{
            objCopy[key] = obj[key] //不是引用类型直接赋值
        }
       }
    }
    return objCopy
}

const res = deepCopy(obj)
console.log(res) // { name: '啊纬', age: 18, like: { type: 'coding' } }
obj.like.type = 'sleeping'
//原对象引用类型数据改变,深拷贝得到的对象数据不发生改变
console.log(res) // { name: '啊纬', age: 18, like: { type: 'coding' } }

这样深拷贝的核心代码我们就成功实现啦~当然这里的代码优化和浅拷贝一样:

//手动实现深拷贝
const obj = {
    name:'啊纬',
    age:18,
    like:{
        type:'coding'
    }
}

function deepCopy(obj) {
    if(typeof obj !== 'object' || obj == null) return //不是对象或者是null直接返回
    const objCopy = obj instanceof Array ? [] : {} //判断obj是数组还是对象
    for (const key in obj) {
       if (obj.hasOwnProperty(key)) {
        if(obj[key] instanceof Object){ //判断obj[key]是不是引用类型
            objCopy[key] = deepCopy(obj[key])
        }  
        else{
            objCopy[key] = obj[key]
        }
       }
    }
    return objCopy
}

const res = deepCopy(obj)
console.log(res) // { name: '啊纬', age: 18, like: { type: 'coding' } }
obj.like.type = 'sleeping'
console.log(res) // { name: '啊纬', age: 18, like: { type: 'coding' } }

总结

如果觉得文章对您有所帮助的话,麻烦给小米露点点关注,点点赞咯😘,有问题欢迎各位小伙伴评论喔😊~ 这是本人的gitee仓库地址:gitee.com/xiaomi-dew/… 。写的各项目代码源码,学习代码和各种资源都在里面啦~如果觉得还不错的话,可以给我的小仓库也点点Star喔😍😍😍 ~