浅拷贝
创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
默认情况下:引用类型(object)都是浅拷贝
简单理解:对于对象来说,就是对最外层数据的拷贝,如果对象只有一层,则相当于深拷贝,如果对象有多层,即某个属性为引用数据类型,则拷贝的是该引用类型在堆中的地址,不会在内存创建一个新的对象。
常见的对象浅拷贝
1. Object.assign
用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
2.展开运算符...
// 对象的浅拷贝
// 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合并为一个新数组并返回
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
)。原始数组不会被改变。
// 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 扩展运算符 ...
// 3.展开运算符
let arr1 = ['哈哈',18,{name:'哈利',age:10}]
let arr5 = [...arr1]
arr1[2].age=5 //arr5会改变
console.log(arr5)
参考:https://github.com/YvetteLau/Step-By-Step/issues/17https://github.com/YvetteLau/Step-By-Step/issues/17
深拷贝
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
根据拷贝的层级不同可以分为浅拷贝和深拷贝,浅拷贝就是只进行一层拷贝,深拷贝就是无限层级拷贝
1.深拷贝的简单实现 JSON.parse(JSON.stringify())
// 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 BigIntmap: 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.完整的深拷贝
封装一个函数来实现:
// 可继续遍历的数据类型
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);// 处理Mapif (typeOfValue === mapTag) {value.forEach((item, key) => {copyValue.set(key, clone(item, map));});return copyValue;}// 处理setif (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类型的keysconst 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;}
}
参考:如何写出一个惊艳面试官的深拷贝 (qq.com)