javascript的深拷贝和浅拷贝

745 阅读4分钟


 这个面试题属于比较常见的面试题,但是我们在回答过程中但是还是有很多细节我们没有回答出来,那么接我们一起来探讨一下这个问题 


为什么会出现深拷贝浅拷贝?

      由于 javascript 存储的方式是堆栈存储,在 javascript 中分为两类数据类型,一类是简单数据类型,一类是复杂数据类型


    简单数据类型:

  • String
  • Number
  • Boolean
  • undefined
  • null
  • Symbol


     简单数据类型的存储方式

// 首先简单数据类型存储来栈中
let a = 1;
let b = a;
    b = 2
// 在存储的时候发生了什么事情



解析:

  1. 申明变量 a 并且赋值 1 ,这个时候由于 a 存储的是简单数据类型,所以在栈中开辟空间进行存储
  2. a 赋值给 b 的过程中,其实是将 a 的值赋值给 b ,a 属于简单数据类,那么,就在栈中新开辟空间进行存储
  3. 这个时候给 b 重新赋值的时候,a 并不会受到影响,还是原来的值


复杂数据类型:

  • Object
  • Array
  • Function
  • Date
  • RegExp
  • ...

复杂数据类型存储方式

let person1 = {
    name:"张三"
}
let person2 = person1
    person2.name = "李四"

console.log(person1.name) // 李四



解析:

  1. 申明 person1 对象,但是 person1 的内容存在堆中,栈中存着指向这个堆的地址
  2. 这个时候在对 person2 进行赋值的时候,赋值的不是内容,而是栈中的地址,这个时候 person1 和 person2 都指向一个内容
  3. 在对 person2 的值进行更改的是,其实是通过地址找到了内容进行更改地址的内容
  4. 那么 内容都改变了,这个时候打印 person1 的时候,就会发现值也改变了
  5. 这种现象就叫做浅拷贝,浅拷贝的危害是会污染到源数据


问题:出现浅拷贝会污染到数据源,在做项目的过程中,经常会出现莫名奇妙的错误,很多情况下就是因为浅拷贝的问题,所以浅拷贝出现,我们需要进行深拷贝,深拷贝说白了,就是在堆中重新开辟空间去存储内容,那么现在有哪些深拷贝的方式呢?


深拷贝的方式有哪些?


1、JSON.parse(JSON.stringify())

let person1 = {
    name:"张三"
}
let person2 = JSON.parse(JSON.stringify(person1))
    person2.name = "李四"
console.log(person1.name) // 张三

看似好像可以实现深拷贝,源数据没有更改,但是 JSON.parse(JSON.stringify())有它自身的问题

 
   let obj = {        
        name:'张三',        
        age:undefined,        
        fn:function(){            
            console.log(1)        
        },        
        data:new Date(),        
        reg:/^\d$/,        
        person:{            
            name:'王五'        
        }    
    }
let obj1 = JSON.parse(JSON.stringify(obj))    
obj1.person.name = "李四"    
console.log(obj1,obj)


解析:我们发现确实是实现了深拷贝,源数据的 "王五" 并没有更改,但是也暴露了问题

问题:

  1. 拷贝之后的 data 源数据是对象,但是拷贝之后变成了字符串
  2. 函数无法拷贝
  3. 正则无法拷贝
  4. undefined 无法拷贝


2、Object.assign()

let person1 = {
    name:"张三"
}
let person2 = {}
    Object.assign(person2,person1)
    person2.name = "李四"
console.log(person1.name) // 张三

看似好像可以实现深拷贝,源数据没有更改,但是 Object.assign()也有它的问题

    let obj = {        
        name:'张三',        
        age:undefined,        
        fn:function(){            
            console.log(1)        
        },        
        data:new Date(),        
        reg:/^\d$/,        
        perosn:{            
            name:'王五',            
            obj:{                
                age:15            
            }       
        }    
    }    
    let obj1 = {}    
    Object.assign(obj1,obj)    
    obj1.perosn.name = "李四"    
    console.log(obj1,obj)


解析:Object.assign能实现拷贝,但是问题和优势很明显

问题:

  1. 第一层可以实现深拷贝,但是二层一下的对象无法实现深拷贝,我们看到,在更改person.name 的值的时候,发现源数据也发生的更改

优势:

  1. 可以实现 undefined 的拷贝
  2. 可以实现对函数的拷贝
  3. 可以实现对 Date 对象的拷贝
  4. 可以实现正则的拷贝

跟Object.assign有着一样的问题的还有扩展运算符


3、扩展运算符

    let obj = {        
       name:'张三',        
       age:undefined,        
       fn:function(){            
          console.log(1)        
       },        
       data:new Date(),        
       reg:/^\d$/,        
       perosn:{            
           name:'王五',            
           obj:{                
              age:15            
           }        
       }    
     }
 let obj1 = {...obj} 
obj1.perosn.name = '李四' 
console.log(obj1,obj)


解析:它的结果跟 Object.assign 一样


4、MessageChannle 管道

  let obj = {        
        name:'张三',        
        age:undefined,        
        // fn:function(){        
        //     console.log(1)        
        // },        
        data:new Date(),        
        reg:/^\d$/,        
        perosn:{            
            name:'王五',            
            obj:{                
                age:15            
            }        
        }    
    }
// 两个对象 prot1 port2  new MessageChannle()    
// 处理异步的问题 promise : 异步中的微任务    
// resolve:成功的回调 reject:失败的回调    
// port1 ======== port2    
function deepCopy(obj){        
       return new Promise(resolve=>{            
                    let {port1,port2} = new MessageChannel()            
                    port2.onmessage = ev => resolve(ev.data)             
                    port1.postMessage(obj)        
        })    
}    
deepCopy(obj).then(res=>{        
    let obj1 = res             
    obj1.name = "李四"        
    obj1.perosn.name = "小五"        
    console.log(obj1,obj)    
}).catch(err=>{        
    console.log(err)    
})


解析:MessageChannle 可以实现深拷贝,并且可以实现其他数据的拷贝

优势:

  1. 可以实现 undefined 的拷贝
  2. 可以实现对函数的拷贝
  3. 可以实现对 Date 对象的拷贝
  4. 可以实现正则的拷贝

问题:

  1. 对函数不能实现拷贝,拷贝函数就会报错


5、自己简单实现递归拷贝

function deepCopy(obj){        
    let newObj = null        
    if(typeof obj ==='object'&&obj !== null){                     
      newObj = obj instanceof Array?[]:{}            
      for(var i in obj){                
          newObj[i] = deepCopy(obj[i])             
      }        
    }else{            
         newObj = obj // 最终会走这一步        
     }        
         return newObj    
}

解释:当然这个拷贝是最简单的,也存在一些问题,但是实现递归最简单的方式就是这个,很多情况下我们为了保证不会出问题,都会用第三方工具库  lodash ,这样写出来才更加保险一点


最后:本人喜欢研究面试题,希望有更多志同道合的朋友一起来交流研究,可以帮助修改简历