何为浅拷贝、深拷贝?我们在项目中可能会经常对数组、对象进行备份 但是你发现我们操作原数组或对象时,会把备份的数据也改变了。
这种情况下,你或与会感到疑惑,此时就需要你对拷贝有一点深入的了解
一、浅拷贝
1. 定义
只是把数组、对象的第一级拷贝,赋值给新的数组。一般我们实现的数组拷贝方法都是浅拷贝
2. 方法
(1) slice(针对数组) 基于当前数组中一个或多个创建新数组 arr.slice(0) ->从arr数组的索引为0的位置到最后一个,截取到新数组
var arr=[5,'hello',true,undefined,[4,2,1],{n:2}]; var arr2=arr.slice(0) console.log(arr2)// [5,'hello',true,undefined,[4,2,1],{n:2}] console.log(arr2[5]===arr[5]);// true arr[5].n="change" // 改变arr中{n:2}的值 console.log(arr2[5].n) //arr2中的{n:2}中n的值也变为change
注: 由上图的arr[5].n="change"改变了,arr2[5].n的值也改变,可以看出浅拷贝只拷贝了第一层,对于第一层中的对象,数组,也只是拷贝了他们的引用(也就是指向同一个地址),而非重新开辟一个新的内存空间。
所以无论是对arr的{n:2}或[4,2,1]进行修改,arr2 都会反映出修改
(2)concat(针对数组) 基于当前数组中所有项创建新数组
var arr=[5,'hello',true,undefined,[4,2,1],{n:2}]; var arr2=arr.concat() console.log(arr2)// [5,'hello',true,undefined,[4,2,1],{n:2}] console.log(arr2[5]===arr[5]);// true
arr[5].n="change" // 改变arr中{n:2}的值 console.log(arr2[5].n) //arr2中的{n:2}中n的值也变为change
(3)扩展运算符(对象数组)
var arr=[5,'hello',true,undefined,[4,2,1],{n:2}]; var arr2=[...arr] console.log(arr2)// [5,'hello',true,undefined,[4,2,1],{n:2}] console.log(arr2[5]===arr[5]);// true arr[5].n="change" // 改变arr中{n:2}的值 console.log(arr2[5].n) //arr2中的{n:2}中n的值也变为change // 如果拷贝是对象 var obj={a:1,name:"joy"}; var obj2={...obj} console.log(obj2)// {a: 1, name: "joy"}
(4)object.assign()(对象数组都可)- >能够拷贝源对象自身的并且可枚举的属性到目标对象
// 如果拷贝是数组 var arr=[5,'hello',true,undefined,[4,2,1],{n:2}]; var arr2=Object.assign([],arr) console.log(arr2)// [5,'hello',true,undefined,[4,2,1],{n:2}] // 如果拷贝是对象 var obj={a:1,name:"joy"}; var obj2=Object.assign({},obj) console.log(obj2)/a/ {a: 1, name: "joy"}
(5)自定义方法(对象数组都可)
let obj={ a:10, b:[0,20,5], c:{x:10}, d:/^d+$/ } let obj2={} // for-in 会遍历出对象和其原型上可枚举的属性 for(let key in obj){ // 判断该属性是否为对象本身的属性 if(!obj.hasOwnProperty(key)) break; obj2[key]=obj[key] } console.log(obj2); //{a: 10, b: Array(3), c: {x:10}, d: /^d+$/} console.log(obj2===obj) //false console.log(obj2.c===obj.c) //true obj2.c.x=200; console.log(obj.c.x) //200
二、深拷贝
1.定义
不仅把第一级拷贝一份新的对象,如果原始对象中存在多级,那么是把每一级都拷贝一份复制给新对象的每一个级别
2.方法
(1)利用JSON数据格式
应用:对数字、字符串、布尔值、普通对象、数组等没有影响
缺点:JSON.stringify(obj)并非symbol对所有值都有处理,正则 => {},函数 / undefined / Symbol => null ,日期格式变成字符串后,基于parse回不到对象格式了
let obj={ a:10, b:[0,20,5], c:{x:10}, d:/^d+$/, e:new Date(), f:function(){return "hello"}, h:undefined } console.log(new Date()) //日期格式Tue Sep 08 2020 20:18:59 GMT+0800 let obj2=JSON.parse(JSON.stringify(obj)); console.log(obj2) //{a: 10, b:[0,20,5], c:{x:10}, d: {}, e: "2020-09-08T12:21:34.834Z"} // 由结果可看出,JSON.stringify(obj)会将日期格式变成字符串后基于parse回不到对象格式了 // 正则变为{},函数/undefind都变成了null
注:JSON.stringify(obj)->先把原始对象变为一个字符串,去除堆与堆嵌套关系
JSON.parse(JSON.stringify(obj))->把字符串转化为新对象,这样浏览器会重新开辟内存来存储信息
(2)自己封装
let obj={ a:10, b:[0,20,5], c:{x:10}, d:/^d+$/, e:new Date(), f:function(){}, h:undefined } function deepClone(obj){ // 过滤出特殊情况 if(obj===null){ return null } if(typeof obj !=='object') return obj; if(obj instanceof RegExp){ return new RegExp(obj) } if(obj instanceof Date){ return new Date(obj) } // obj.constructor 不直接创建空对象的目的:拷贝的结果和之前保持相同的属类 let newObj=new obj.constructor(); for(let key in obj){ if(!obj.hasOwnProperty(key)) break; newObj[key]=deepClone(obj[key]) } return newObj; } let obj2=deepClone(obj); console.log(obj2)
console.log(obj===obj2) //false console.log(obj.c===obj2.c) //fasle // 数组 // let obj=[10,[0,20,5], // {x:10},/^d+$/, // new Date(),undefined,function(){} // ] // let obj2=deepClone(obj); // console.log(obj2)
// console.log(obj===obj2) //false // console.log(obj.c===obj2.c) //fasle