js中的深浅拷贝

文章类型:Javascript

发布者:admin

发布时间:2023-02-28

一:类型

javascript变量包含两种不同数据类型的值:基本类型和引用类型

1:基本类型String、Number、Boolean、Null、Undefined、Symbol

2:引用类型Object(Object、Array、Function)

二:存储方式

1:栈:自动分配内存空间,系统自动释放,里面存放的是基本类型的值和引用类型的地址

2:堆:动态分配的内存,大小不定,也不会自动释放。里面存放引用类型的值

三:传递方式

1:基本类型采取值传递方式

2:引用类型采取地址传递方式

四:定义

1:浅拷贝:复制对象的时候,只对第一层进行复制,深层对象还是指向原有地址

2:深拷贝:复制整个对象,以及深层次的嵌套对象,两者相互分离,互不影响

六:方式

1:浅拷贝

原理:创建一个新的对象,来接受你要重新复制或引用的对象值

实现:

a:object.assign 多个js对象的合并,只能拷贝浅层

let target = {};
let source = { a: { b: 1 } };
Object.assign(target, source);
console.log(target); // { a: { b: 1 } };

b:扩展运算方式在构造对象的同时完成浅拷贝的功能

let obj = {a:1,b:{c:1}}
let obj2 = {...obj}

c:concat拷贝数组 连接一个含有引用类型的数组时,需要注意修改原数组中的元素的属性,因为它会影响拷贝之后连接的数组

let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1] = 100;
console.log(arr); // [ 1, 2, 3 ]
console.log(newArr); // [ 1, 100, 3 ]

d:slice 拷贝数组,返回一个新的数组对象

let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr); //[ 1, 2, { val: 1000 } ]

e:手工实现,主要是对基本类型拷贝,对引用类型开辟新的存储,拷贝一层对象数据

	function myShallow(target){
if(target!=null && typeof target=='object'){
const newData=Array.isArray(target) ? [] :{}
for(let item in target){
if(target.hasOwnProperty(item)){
newData[item]=target[item]
}
}
return newData
}else{
return target
}
}
let obj={
a:1,
b:{
name:'测试',
c:{
age:10
}
}
}
let newObj=myShallow(obj)
console.log(newObj)

2:深拷贝

原理:将一个对象完整拷贝一份给目标对象,并且从堆内存开辟全新的空间存放

1:JSON.stringify,把对象序列化成字符串,然后再转成新的对象,

注意:该方法有局限性,慎用,像函数、undefined、symbol等

let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE

2:手写方式1 通过递归进行 for in 遍历,不能复制不可枚举的属性以及 Symbol 类型针对普通的引用类型的值做递归复制,

对于 Array、Date、RegExp、Error、Function 这样的引用类型并不能正确地拷贝;

对象的属性里面成环,即循环引用没有解决

	function myShallow(target){
if(target!=null && typeof target=='object'){
const newData=Array.isArray(target) ? [] :{}
for(let item in target){
if(target[item]!=null && typeof target[item]=='object'){
newData[item]=myShallow(target[item])
}else{
if(target.hasOwnProperty(item)){
newData[item]=target[item]
}
}

}
return newData
}else{
return target
}
}
let obj={
a:1,
b:{
name:'测试',
c:{
age:10
}
}
}
let newObj=myShallow(obj)
console.log(newObj)

改进版本,进行Date、RegExp、Error加入验证

function myShallow(target,item){
const newData=Array.isArray(target) ? [] :{}
if (Object.prototype.toString.call(target) === '[object Date]' ){
newData[item]=new Date(target)
}
if (Object.prototype.toString.call(target) === '[object RegExp]' ){
newData[item]=new RegExp(target)
}
if (Object.prototype.toString.call(target) === '[object Error]' ){
newData[item]=new Error(target)
}
if(target!=null && typeof target=='object'){
for(let item in target){
if(target[item]!=null && typeof target[item]=='object'){
newData[item]=myShallow(target[item],item)
}else{
if(target.hasOwnProperty(item)){
newData[item]=target[item]
}
}

}
return newData
}else{
return target
}
}
let obj={
a:1,
b:{
date: new Date(),
reg: new RegExp('/我是一个正则/ig'),
name:'测试',
c:{
age:10
}
}
}
let newObj=myShallow(obj)
console.log(newObj)

改进2 增加函数方式判断 ,主要根据普通函数和箭头函数, 区分这两者只需要看有无prototype, 有prototype属性就属于普通函数, 没有就是箭头函数

	function myShallow(target,item){
const newData=Array.isArray(target) ? [] :{}
if(typeof target==='function'){
newData[item]=target[item].prototype? eval(`(${target[item].toString()})`) : eval(target[item].toString())
}

if (Object.prototype.toString.call(target) === '[object Date]' ){
newData[item]=new Date(target)
}
if (Object.prototype.toString.call(target) === '[object RegExp]' ){
newData[item]=new RegExp(target)
}
if (Object.prototype.toString.call(target) === '[object Error]' ){
newData[item]=new Error(target)
}
if(target!=null && typeof target=='object'){
for(let item in target){
if(target[item]!=null && typeof target[item]=='object'){
newData[item]=myShallow(target[item],item)
}else{
if(target.hasOwnProperty(item)){
newData[item]=target[item]
}
}

}
return newData
}else{
return target
}
}
let obj={
a:1,
b:{
date: new Date(),
reg: new RegExp('/我是一个正则/ig'),
func: function () { console.log('我是一个函数') },
name:'测试',
c:{
age:10
}
}
}
let newObj=myShallow(obj)
console.log('原值')
console.log(obj)
console.log('新值')
console.log(newObj)

改进3

3:手写方式2,进行递归实现,改进上面写法的不足针对Symbol、Date、RegExp,

针对Symbol使用Reflect.ownKeys

针对Date、RegExp 类型,则直接生成一个新的实例返回

利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性,以及对应的特性,顺便结合 Object.create 方法创建一个新对象,并继承传入原对象的原型链;

利用 WeakMap 类型作为 Hash 表,因为 WeakMap 是弱引用类型,可以有效防止内存泄漏作为检测循环引用很有帮助,如果存在循环,则引用直接返回 WeakMap 存储的值

const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)

const deepClone = function (obj, hash = new WeakMap()) {
if (obj.constructor === Date) {
return new Date(obj) // 日期对象直接返回一个新的日期对象
}

if (obj.constructor === RegExp){
return new RegExp(obj) //正则对象直接返回一个新的正则对象
}

//如果循环引用了就用 weakMap 来解决
if (hash.has(obj)) {
return hash.get(obj)
}
let allDesc = Object.getOwnPropertyDescriptors(obj)

//遍历传入参数所有键的特性
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)

// 把cloneObj原型复制到obj上
hash.set(obj, cloneObj)

for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
}
return cloneObj
}