当初准备面试的时候有了解过深拷贝浅拷贝,不过当时也了解的比较皮毛,通俗地讲,深拷贝就是开拓一个新的内存,将原对象的内容放进去,用一个新对象指向,浅拷贝就是大家共用一个内存块。
浅拷贝与深拷贝
浅拷贝:首先创建一个新对象,如果复制的是基本类型的数据,那就是复制一个基本类型的值;如果拷贝的是一个引用类型的数据,那复制的是一个内存地址,不管新对象还是旧对象,一旦有人更改了这个引用属性,那么大家的该属性都会改变
深拷贝:在内存中开辟一个全新的地址,将旧对象的所有属性给复制到这个新内存,更改新对象数据后不影响旧对象的数据
赋值和深浅拷贝
赋值与浅拷贝看起来是一样的道理,实际上赋值是新对象所有的属性都与旧对象属性的地址共用,而浅拷贝的基本类型属性是存放于新内存中的,引用类型是共用的
简单的浅拷贝深拷贝
浅拷贝
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)
我们可以发现,通过这个方法拷贝的新对象,无法拷贝函数属性,正则属性变成空对象,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
}
结果如下:
如图,简单的for in 递归还是无法处理函数和特殊类型属性
而且当有环时,会出现报错和爆栈的情况
什么是环:
手写递归
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
}