JS中的深拷贝与浅拷贝(最近更新2021/8/19)

141 阅读2分钟

当初准备面试的时候有了解过深拷贝浅拷贝,不过当时也了解的比较皮毛,通俗地讲,深拷贝就是开拓一个新的内存,将原对象的内容放进去,用一个新对象指向,浅拷贝就是大家共用一个内存块。

浅拷贝与深拷贝

浅拷贝:首先创建一个新对象,如果复制的是基本类型的数据,那就是复制一个基本类型的值;如果拷贝的是一个引用类型的数据,那复制的是一个内存地址,不管新对象还是旧对象,一旦有人更改了这个引用属性,那么大家的该属性都会改变

深拷贝:在内存中开辟一个全新的地址,将旧对象的所有属性给复制到这个新内存,更改新对象数据后不影响旧对象的数据

赋值和深浅拷贝

赋值与浅拷贝看起来是一样的道理,实际上赋值是新对象所有的属性都与旧对象属性的地址共用,而浅拷贝的基本类型属性是存放于新内存中的,引用类型是共用的

简单的浅拷贝深拷贝

浅拷贝

Object.assign

let obj1={
  name:'BD',
  info:{
    school:'sztu',
    address:{
      a:123
    }
  }
}
let obj2=Object.assign({},obj1)
obj1.name='bd'
obj1.info.address.a=789 // 修改obj1
obj2.info.school='szu'  // 修改obj2

console.log('obj1',obj1) //obj1 { name: 'bd',info: { school: 'szu', address: { a: 789 } } }
console.log('obj2',obj2) //obj2 { name: 'BD',info: { school: 'szu', address: { a: 789 } } }

Object.assign 对于基本类型的属性直接复制,互不影响,而对于引用类型的属性则是浅拷贝

附上 Object.assign 的 polyfill

// Polyfill
if(typeof Object.assign!== 'function'){
  Object.defineProperty(Object,'assign',{
    value:function(targetObj,sourceObj){
      if(targetObj == null || targetObj == undefined){
        throw new TypeError('can not covert null or undefined to a Object')
      }
      var newObj=Object(targetObj)

      for(var index=1;index<arguments.length;i++){
        var nextSource=arguments[index]
        for(var key in nextSource){
          // 之所以用这么调用hasOwnProperty是因为hasOwnProperty可能是个属性
          if (Object.prototype.hasOwnProperty.call(nextSource, key)) {
            newObj[key]=nextSource[key]
          }
          // if(nextSource.hasOwnProperty(key)){
          //   newObj[key]=nextSource[key]
          // }
        }
      }
      return newObj
    },
    configurable:true,
    writable:true,
    enumerable:false
  })
}

Array.prototype.slice()

let str1=[1,{num:2},4]
let str2=str1.slice()
str1[0]=0
str2[1].num=0
console.log('str1',str1) // str1 [ 0, { num: 0 }, 4 ]
console.log('str2',str2) // str2 [ 1, { num: 0 }, 4 ]

Array.prototype.concat()

let str1=[1,{num:2},4]
let str2=str1.concat()
str1[0]=0
str2[1].num=0
console.log('str1',str1) // str1 [ 0, { num: 0 }, 4 ]
console.log('str2',str2) // str2 [ 1, { num: 0 }, 4 ]

深拷贝

JSON.parse(JSON.stringify(obj))

let obj1={
  name:'bd',
  info:{
    school:'sztu',
    age:18
  }
}
let obj2=JSON.parse(JSON.stringify(obj1))
obj1.name='BD'
obj2.info.age=22
console.log('obj1',obj1)
console.log('obj2',obj2)

这个方法能解决大部分的深拷贝问题,但也存在不足,例如,当对象有函数、正则或date时,便不能处理了

let obj1={
  name:'bd',
  info:{
    school:'sztu',
    age:18
  },
  fun:function(){
    console.log('yes')
  },
  reg:/正则/ig,
  time:new Date()
}
let obj2=JSON.parse(JSON.stringify(obj1))
console.log('obj1',obj1)
console.log('obj2',obj2)

image.png

image.png

我们可以发现,通过这个方法拷贝的新对象,无法拷贝函数属性,正则属性变成空对象,date类型属性直接变成了字符串

for in 递归

function isObj(obj){
  return (typeof obj=== 'object' || typeof obj=== 'function') && obj!==null
}

function deepClone(obj){
  let newObj= Array.isArray(obj)? []:{}
  for(let key in obj){
    newObj[key]=isObj(obj[key])? deepClone(obj[key]):obj[key]
  }
  return newObj
}

结果如下:

image.png

如图,简单的for in 递归还是无法处理函数和特殊类型属性

而且当有环时,会出现报错和爆栈的情况

什么是环:

image.png

手写递归

function deepClone(obj,hash=new WeakMap()){
    if(obj == null ) return obj
    if(typeof obj !== 'object') return obj
    if(obj instanceof Date) return new Date(obj)
    if(obj instanceof RegExp) return new RegExp(obj)
    if(hash.has(obj)) return hash.get(obj)
    var newObj = new obj.constructor()  
    hash.set(obj,newObj)
    for(let key in obj){
        if(Object.prototype.hasOwnProperty.call(obj,key)){
            newObj[key]=deepClone(obj[key],hash)
        }
    }
    return newObj
}

image.png

可爱鸭子.gif