浅拷贝的方法
Object.assign()
- 方法可以将任意多个源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象
Object.assign()
const obj = {
name: 'xiaolizi',
age: 20,
sex: '男'
}
const newObj = Object.assign({}, obj)
const obj1 = Object.assign(obj, {name: 'newxiaolizi', age: 25})
console.log(newObj.name, obj1)
注意点:
- 它不会拷贝对象里的继承属性
- 它不会拷贝对象里的不可枚举属性
- 可以拷贝Symbol类型属性
使用扩展运算符实现浅拷贝
let source = {
name: 'Marry',
info: {
age: 20
}
}
const nameInfos = {...source}
nameInfos.info.age = 24
console.log(source)
- 上述代码和使用Object.assign功能相同,其注意事项也相同,两者在使用上基本是可以相互转换
Array.prototype.concat
- 数组中的concat方法其实也是浅拷贝,使用场景比较少,当用concat连接的数组含有引用类型数据,要注意修改原数组中元素属性,会影响到concat连接产生的新数组。
let student={
name:"Lily",
age:15,
sex:"女",
friends:["Jack","Rose","Ben"]
}
let obj5=Array.prototype.concat.call({}, student, {school: 'xiaoxue'})
student.friends[0] = 'Marry'
student.age = 20
console.log(obj5) //{name: "Lily", age: 20, sex: "女", friends: Array(3)}
对象中age变成20,friends的jack变成Marry。
Array.prototype.slice()
- 数组中slice方法也是浅拷贝,使用场景比较少,同concat
const arr = [1, 2, {name: 'li'}]
const newArr = arr.slice()
newArr[2].name = 'Tom'
console.log(arr, newArr)
arr数组中name的value值变成Tom
使用第三方库&手动实现
- lodash工具库提供的clone的方法供用户浅拷贝
浅拷贝的核心在于:
- 基础数据类型直接拷贝数据
- 引用数据类型仅拷贝第一层对象的属性,复制的就是内存中的地址(深拷贝与浅拷贝的重要差异)
const clone = function (target) {
if(typeof target === 'Object' && target !== null) {
let cloneTarget = Array.isArray(target)? [] : {}
for(let prop in target) {
if(target.hasOwnProperty(prop)){
cloneTarget[prop] = target[prop]
}
}
return cloneTarget
}else {
return target
}
}
let arr = [1, 2, 3, {name: 'Tom'}]
let newArr = clone(arr)
console.log(newArr)
newArr[3].name = 'Marry'
newArr[2].name = 6
console.log(arr)
深拷贝
- 深拷贝是将一个对象从内存中完整的拷贝出一份,从内存中开辟出一个新的区域来存放新对象,新对象跟原对象不共享内存,修改新对象不会影响原对象。
JSON.parse(JSON.stringify())
const obj = {
name: 'xiaolizi',
age: 20,
sex: '男'
}
const newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj)
newObj.name = 'newxiaolizi'
console.log(obj.name, newObj.name)
- 借助工具lodash库来深拷贝
- 利用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的内存空间,实现深拷贝。
- 这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则,因为这两者基于JSON.stringify和JSON.parse处理后,得到的正则就不再是正则(变为空对象),对象中的方法克隆不到。
const obj = {
name: 'xiaolizi',
age: 20,
sex: '男',
say(){
console.log(goodBey)
}
}
obj.say()
const newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj)
newObj.say()
JSON.stringify需要注意的点
- 拷贝对象如果有函数,undefined, symbol这几种类型,经过JSON.stringify系列化之后的字符串中的键值对会消失。
- 拷贝Date引用类型会变成字符串
- 无法拷贝不可枚举的属性
- 无法拷贝对象的原型链
- 拷贝RegExp引用类型会变成空对象
- 对象中含有NAN,infinity以及-infinity,JSON.stringify系列化后会转换成null
- 无法拷贝对象的循环应用,既对象成环 (obj[key] = obj)
递归
function deepClone(oldObj, hash = new WeakMap()) {
if (oldObj === null) return oldObj;
// 如果是null或者undefined我就不进行拷贝操作
if (oldObj instanceof Date) return new Date(oldObj);
if (oldObj instanceof RegExp) return new RegExp(oldObj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof oldObj !== "object") return oldObj;
// 是对象的话就要进行深拷贝
if (hash.get(oldObj)) return hash.get(oldObj);
let cloneObj = new oldObj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(oldObj, cloneObj);
for (let key in oldObj) {
if (oldObj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(oldObj[key], hash);
}
}
return cloneObj;
}
let oldObj = { name: 1, address: { x: 100 } };
oldObj.o = oldObj; // 对象存在循环引用的情况
let d = deepClone(oldObj);
oldObj.address.x = 200;
console.log(d);