浅拷贝
创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
默认情况下:引用类型(object)都是浅拷贝
简单理解:对于对象来说,就是对最外层数据的拷贝,如果对象只有一层,则相当于深拷贝,如果对象有多层,即某个属性为引用数据类型,则拷贝的是该引用类型在堆中的地址,不会在内存创建一个新的对象。
常见的对象浅拷贝
1. Object.assign
用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
2.展开运算符...
javascript
// 对象的浅拷贝
// 1.Object.assign()
let obj0={a:0}
let obj2 = Object.assign(obj0,obj1)
console.log(obj0===obj2) //true
console.log(obj1===obj2) // false
obj1.name = '张三1' //obj2不变
obj1.hobby[0]='打篮球' //obj2变了
obj1.friend.name='李四1' //obj2变了
console.log(obj1)
console.log(obj2)
// 2.展开运算符
let obj3 = {...obj1}
obj1.hobby.push('摄影') //obj3会改变
console.log(obj3)
如果被拷贝对象的属性值为简单类型(如Number,String),通过Object.assign({},Obj)得到的新对象为深拷贝;如果属性值为对象或其它引用类型,则只拷贝了该引用类型的地址。
常见的数组浅拷贝
1.Array.prototype.concat()
将alpha和numeric合并为一个新数组并返回
javascript
let arr1 = ['哈哈',18,{name:'哈利',age:10}]
let arr2 = ['嘿嘿',19,{name:'赫敏',age:11}]
// 1.concat
let arr3 = arr1.concat(arr2)
console.log(arr3===arr1) //false
console.log(arr3===arr2) // false
arr1[0]='haha' //arr3不会改
arr1[2].age=100 //arr3会改
console.log(arr3)
2. Array.prototype.slice()
slice() 方法返回一个新的数组对象,这一对象是一个由 begin
和 end
决定的原数组的浅拷贝 (包括 begin
,不包括end
)。原始数组不会被改变。
javascript
// 1.slice截取数组
const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
console.log(animals.slice(2));
// expected output: Array ["camel", "duck", "elephant"]
console.log(animals.slice(2, 4));
// expected output: Array ["camel", "duck"]
//2.slice浅拷贝
let arr1 = ['哈哈',18,{name:'哈利',age:10}]
let arr4 = arr1.slice() //不传参数相当于浅拷贝数组
console.log(arr4===arr1) //false
arr1[2].name = '哈利111' //arr4会变
console.log(arr4)
3 扩展运算符 ...
javascript
// 3.展开运算符
let arr1 = ['哈哈',18,{name:'哈利',age:10}]
let arr5 = [...arr1]
arr1[2].age=5 //arr5会改变
console.log(arr5)
深拷贝
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
根据拷贝的层级不同可以分为浅拷贝和深拷贝,浅拷贝就是只进行一层拷贝,深拷贝就是无限层级拷贝
1.深拷贝的简单实现 JSON.parse(JSON.stringify())
javascript
// JSON.parse(JSON.stringify(obj))可以处理:
// string,number,boolean,null,object,array
// 会忽略symbol,undefined,function,不能处理bigint,date,error,map,set
// 不能处理循环引用
let obj = {
name: "哈哈",
age: 18,
bool: true,
nu: null,
un: undefined, //会忽略undefined类型的值
symbol: Symbol(), //会忽略symbol类型的值
// big: 15n, Do not know how to serialize a BigInt
map: new Map(), // 不能处理
set: new Set(),// 不能处理
obj: {
color: "blue",
age: 13,
},
arr: [1, 2, 3, 4],
date: new Date(),
err: new Error(),
fun: function add() {}, //会忽略function类型的值
};
let obj1 = JSON.parse(JSON.stringify(obj));
- 可以处理基本数据类型(string,number,boolean,null),和引用数据类型的(对象,数组)
- 不能处理函数,undefined,symbol,function,map,set类型的值
- 并且当对象中包含循环引用时会报错(obj.info = obj)
3.完整的深拷贝
封装一个函数来实现:
javascript
// 可继续遍历的数据类型
const objectTag = "[object Object]";
const arrayTag = "[object Array]";
const mapTag = "[object Map]";
const setTag = "[object Set]";
// 不可继续遍历的数据类型
const stringTag = "[object String]";
const numberTag = "[object Number]";
const booleanTag = "[object Boolean]";
const symbolTag = "[object Symbol]";
const dateTag = "[object Date]";
const functionTag = "[object Function]";
const errorTag = "[object Error]";
// 需要深度遍历的数据类型
const deepTag = [setTag, mapTag, arrayTag, objectTag];
// 工具函数-遍历数组,使用while提升性能
function forEach(arr, cb) {
const len = arr.length;
let i = -1;
while (++i < length) {
cb(arr[i], i);
}
return arr;
}
// 工具函数-判断是否是引用类型
function isObject(value) {
const type = typeof value;
// 这里忽略了function类型 function类型直接返回
return value !== null && type === "object";
}
// 工具函数-获取数据实际类型
function getType(value) {
return Object.prototype.toString.call(value);
}
//工具函数-初始化被克隆的对象
function getInit(value) {
const cons = value.constructor;
return new cons();
}
// 工具函数-克隆symbol
function cloneSymbol(value) {
return Object(Symbol.prototype.valueOf.call(value));
}
// 工具函数-克隆不可遍历的类型(这里忽略了对函数和正则的处理)
function cloneOtherType(value) {
const type = getType(value);
const ctor = value.constructor;
switch (type) {
case stringTag:
case numberTag:
case booleanTag:
case functionTag:
case errorTag:
case dateTag:
return new ctor(value);
case symbolTag:
return cloneSymbol(value);
default:
return null;
}
}
// 真正实现深拷贝的函数
function clone(value, map = new WeakMap()) {
// 不是引用数据类型直接返回
if (!isObject(value)) return value;
// 根据数据类型做不同的处理
let copyValue;
const typeOfValue = getType(value);
if (deepTag.includes(typeOfValue)) {
copyValue = getInit(value);
} else {
return cloneOtherType(value);
}
// 处理循环引用
if (map.has(value)) return map.get(value);
map.set(value, copyValue);
// 处理Map
if (typeOfValue === mapTag) {
value.forEach((item, key) => {
copyValue.set(key, clone(item, map));
});
return copyValue;
}
// 处理set
if (typeOfValue === setTag) {
set.forEach((item) => {
copyValue.add(clone(item, map));
});
return copyValue;
}
// 处理对象
if (typeOfValue === objectTag) {
const keys = value.keys();
forEach(keys, (item) => {
copyValue[item] = clone(value[item], map);
});
//获取属性值为symbol类型的keys
const symbolKeys = Object.getOwnPropertySymbols(value);
forEach(symbolKeys, (item) => {
copyValue[item] = clone(value[item], map);
});
return copyValue;
}
// 处理数组
if (typeOfValue === arrayTag) {
forEach(value, (item, index) => {
copyValue[index] = clone(item, map);
});
return copyValue;
}
}