Js复制对象/克隆对象 Js浅拷贝与深拷贝 浅拷贝和深拷贝的实现方法

2021年10月3日 10点热度 0条评论 来源: 画画的蓓蓓

Js复制对象/克隆对象 Js浅拷贝与深拷贝 浅拷贝和深拷贝的实现方法

前言

学习Js克隆一个对象,作为准备工作,需要理解Js中的数据类型和按值传递:Js中的数据类型和按值传递

浅拷贝最后两种方法涉及到了继承和es5新特性中的call方法,可以读es5替换函数中的this的方法
Js中的prototype、__proto__和constructor

1. 浅拷贝

1.1. 赋值和浅拷贝

概念:

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是原始类型,拷贝的就是原始类型的值;如果属性是引用类型,拷贝的就是内存地址 。

代码示例:

var student={ 
    name:"Lily",
    age:15,
    sex:"女",
    friends:["Jack","Rose","Ben"]
}
var obj1=student;
function clone(obj){ 
    var newObj={ };
    for(var key in obj){ 
        newObj[key]=obj[key]
    }
    return newObj;
}
var obj2=clone(student)
obj1.name="Tom";
obj2.sex="男";
obj2.friends[0]="Lilei";
console.log(student.name) //Tom 
console.log(student.age) //15
console.log(student.friends) //["Lilei", "Rose", "Ben"]

解析:

i. 赋值:

上面一段代码,student变量中保存着所创建对象的地址,obj1由赋值得到,根据按值传递,我们知道,obj1变量中保存的是student所存地址的副本,这两个地址指向同一个实例化对象,无论哪个对象发生改变,都会改变这个实例化对象,两个对象是联动的。

ii. 浅拷贝:

a. 重新创建一个新对象,逐个拷贝源对象的属性;

b. 如果属性值是原始类型数据,拷贝的是原始类型的值的副本,原始类型的属性在新对象和源对象之间互不影响

c. 如果属性值是引用类型数据,拷贝的是地址,如果其中一个的地址发生改变,就会影响到另外一个对象

1.2. 浅拷贝的方法

1.2.1. Object.assign()

Object.assign()方法可以将源对象自身的可枚举属性(任意多个)拷贝给目标对象,然后返回目标对象

var student={ 
    name:"Lily",
    age:15,
    sex:"女",
    friends:["Jack","Rose","Ben"]
}
var obj3=Object.assign({ },student)
console.log(obj3.name) //LilY
1.2.2. 展开运算符…(Es6新特性)
var student={ 
    name:"Lily",
    age:15,
    sex:"女",
    friends:["Jack","Rose","Ben"]
}
var obj4={ ...student}
console.log(obj4.name) //LilY
1.2.3. 强行调用数组的concat方法
var student={ 
    name:"Lily",
    age:15,
    sex:"女",
    friends:["Jack","Rose","Ben"]
}
var obj5=Array.prototype.concat.call({ },student)
console.log(obj5) //{name: "Lily", age: 15, sex: "女", friends: Array(3)} 
1.2.4. 强行调用数组的slice方法
var student={ 
    name:"Lily",
    age:15,
    sex:"女",
    friends:["Jack","Rose","Ben"]
}
var obj6=Array.prototype.slice.call({ },student)
console.log(obj6) //{name: "Lily", age: 15, sex: "女", friends: Array(3)} 

2.深拷贝

概念

深拷贝是将一个对象从内存中完整的拷贝出一份,从内存中开辟出一个新的区域来存放新对象,新对象跟原对象不共享内存,修改新对象不会影响原对象。

2.1. 深拷贝与浅拷贝对比

2.2. 深拷贝的实现方式

2.2.1. JSON.parce(JSON.stringify())
var student={ 
    name:"Lily",
    age:15,
    sex:"女",
    friends:["Jack","Rose","Ben"]
}
var obj7=JSON.parse(JSON.stringify(student))
console.log(obj7) //{name: "Lily", age: 15, sex: "女", friends: Array(3)}
obj7.friends[0]="Alice"
console.log(student.friends[0]) //Jack 未影响源对象

利用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的内存空间,实现深拷贝。

这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则,因为这两者基于JSON.stringify和JSON.parse处理后,JSON.stringify()方法在处理undefined、任意的函数以及 symbol 值时,作为非数组对象的属性值时会被忽略,作为数组对象中的属性值时,会被转换成 null。函数、undefined 被单独转换时,会返回 undefined。

使用JSON.parce(JSON.stringify())克隆非数组对象,源对象中的方法被忽略
var student={ 
    name:"Lily",
    age:15,
    sex:"女",
    friends:["Jack","Rose","Ben"],
    say:function(){ 
        console.log("hello")
    }
}
var obj7=JSON.parse(JSON.stringify(student))
console.log(obj7) //{name: "Lily", age: 15, sex: "女", friends: Array(3)} say方法被忽略
使用JSON.parce(JSON.stringify())克隆数组对象,源对象中的方法被转换成null
var arr= [1,2,3,function say(){ console.log("hello")}];
var obj=JSON.parse(JSON.stringify(arr));
console.log(arr,obj)


这里列举了一些最常用情况下的弊端,其他方面的弊端可参考JSON.stringify()

2.2.2.手写递归

递归方法实现深度克隆原理:遍历对象直到最内层都是原始数据类型,然后再去复制,就是深度拷贝
有种特殊情况需注意就是对象存在循环引用的情况,即对象的属性直接的引用了自身的情况,解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝。

function deepClone(obj, hash = new WeakMap()) { 
  if (obj === null) return obj; 
  // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;
  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
  hash.set(obj, cloneObj);
  for (let key in obj) { 
    if (obj.hasOwnProperty(key)) { 
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}
let obj = {  name: 1, address: {  x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);
解析:

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。
方法:

WeakMap.prototype.delete(key)
移除key的关联对象。执行后 WeakMap.prototype.has(key)返回false。
WeakMap.prototype.get(key)
返回key关联对象, 或者 undefined(没有key关联对象时)。
WeakMap.prototype.has(key)
根据是否有key关联对象返回一个Boolean值。
WeakMap.prototype.set(key, value)
在WeakMap中设置一组key关联对象,返回这个 WeakMap对象

参考文章:
如何写出一个惊艳面试官的深拷贝?(找不到文章的网址,只能贴个题目)
一文读懂 javascript 深拷贝与浅拷贝(公众号推文,找不到文章的网址,只能贴个题目)
WeakMap 对象

    原文作者:画画的蓓蓓
    原文地址: https://blog.csdn.net/Amazing_rabbit/article/details/108648270
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。