深拷贝:指创建一个与原对象完全独立的新对象,新对象内部所有的属性值都与原对象相同,但两者在内存中的地址不同(修改新对象不会影响原对象)。在 JavaScript 中实现深拷贝可以借助递归和类型判断的方法。
function deepCopy(obj) {
// 判断数组或对象
if (typeof obj !== 'object' || obj === null) {
return obj;
}
// 初始化结果
let result;
if (obj instanceof Array) {
result = [];
} else {
result = {};
}
// 遍历对象或数组
for (let key in obj) {
// 判断属性值是否为对象或数组,如果是则递归深拷贝
if (typeof obj[key] === 'object' && obj[key] !== null) {
result[key] = deepCopy(obj[key]);
} else {
result[key] = obj[key];
}
}
return result;
}
深拷贝示例
const obj1 = { name: 'Tom', age: 20, hobbies: ['swimming', 'jogging'] };
const obj2 = deepCopy(obj1);
console.log(obj2); // {name: 'Tom', age: 20, hobbies: ['swimming', 'jogging']}
console.log(obj1 === obj2); // false
解释: 本例中,deepCopy函数遍历对象或数组的每一个属性,如果该属性值是对象或数组,则递归执行deepCopy函数,将该属性值深度拷贝到新对象中。最终返回一个与原对象完全独立的新对象。
浅拷贝:指创建一个新的对象,这个新对象与原对象的基本数据类型的属性值完全相同,但是对象内部的引用类型的属性值会指向原对象内部引用类型属性的内存地址,而不是新开辟一块内存空间。因此,在原对象和新对象之间存在引用关系。在 JavaScript 中实现浅拷贝可以借助对象的
Object.assign()
方法或扩展运算符...
。(只复制指向某个对象的指针,不复制对象本身,新旧对象还是共享同一块内存。修改新对象会影响原对象
)
浅拷贝示例:
const obj1 = { name: 'Tom', age: 20, hobbies: ['swimming', 'jogging'] };
const obj2 = Object.assign({}, obj1); // 或者使用 const obj2 = { ...obj1 };
console.log(obj2); // {name: 'Tom', age: 20, hobbies: ['swimming', 'jogging']}
console.log(obj1 === obj2); // false
console.log(obj1.hobbies === obj2.hobbies); // true,obj2 的 hobbies 属性与 obj1 的 hobbies 属性指向同一块内存。
解释:本例中, Object.assign() 方法创建了一个新对象 obj2并将obj1的属性复制到obj2中。由于 obj1中的hobbies属性值为数组,因此obj2中的hobbies属性值也是数组,并且它们指向同一块内存空间,即obj2.hobbies和obj1.hobbies引用同一个数组。因此修改obj2.hobbies数组中的元素,会影响obj1.hobbies数组。
浅拷贝方法
1、Object.assign()
Object.assign() 是 ES6 中对象的拷贝方法,接受的第一个参数是目标对象,其余参数是源对象,用法:Object.assign(target, source_1, ···),该方法可以实现浅拷贝,也可以实现一维对象的深拷贝。
该方法需要注意的是:
如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。
因为null 和 undefined 不能转化为对象,所以第一个参数不能为null或 undefined,会报错。
let target = {a: 1};
let object2 = {b: 2};
let object3 = {c: 3};
Object.assign(target,object2,object3);
console.log(target); // {a: 1, b: 2, c: 3}
2、扩展运算符:使用扩展运算符可以在对对象或数组的第二层开始的拷贝是浅拷贝。
let obj1 = {a:1,b:{c:1}}
let obj2 = {...obj1};
obj1.a = 2;
console.log(obj1); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}
obj1.b.c = 2;
console.log(obj1); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}
3、数组方法实现数组浅拷贝(该方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝)
(1)Array.prototype.slice
let arr = [1,2,3,4];
console.log(arr.slice()); // [1,2,3,4]
console.log(arr.slice() === arr); //false
(2)Array.prototype.concat(concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。该方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝。)
let arr = [1,2,3,4];
console.log(arr.concat()); // [1,2,3,4]
console.log(arr.concat() === arr); //false
4、实现浅拷贝函数
// 浅拷贝的实现;
function shallowCopy(object) {
// 只拷贝对象
if (!object || typeof object !== "object") return;
// 根据 object 的类型判断是新建一个数组还是对象
let newObject = Array.isArray(object) ? [] : {};
// 遍历 object,并且判断是 object 的属性才拷贝
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] = object[key];
}
}
return newObject;
}
实际应用:比如下拉框的条件筛选中有的包含全部选项,有的则不包含就用到了——深拷贝
拷贝的实现方法:
实际的项目开发过程中,在多数情况下不希望将对象进行浅拷贝,因为值会相互影响,容易出错,可以把浅拷贝转换成深拷贝。
1、JSON.stringify()
JSON.parse(JSON.stringify(obj)) 是目前比较常用的深拷贝方法之一,它的原理就是利用 JSON.stringify 将 js 对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象。
let obj1 = { a: 0,b: {c: 0}};
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.a = 1;
obj1.b.c = 1;
console.log(obj1); // {a: 1, b: {c: 1}}
console.log(obj2); // {a: 0, b: {c: 0}}
注意:这个方法可以简单粗暴的实现深拷贝,但是还存在问题,拷贝的对象中如果有函数、undefined、symbol,当使用过 JSON.stringify() 进行处理之后,都会消失。
2、函数库lodash的_.cloneDeep方法,该函数库也有提供_.cloneDeep用来做 Deep Copy
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
3、扩展运算符:使用扩展运算符可以在对对象或数组的第一层的拷贝是深拷贝。
let obj1 = {a:1,b:{c:1}}
let obj2 = {...obj1};
obj1.a = 2;
console.log(obj1); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}
obj1.b.c = 2;
console.log(obj1); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}
4、实现深拷贝函数(解决循环引用)
// 深拷贝的实现1
function deepCopy(obj, map) {
//判断是否是第一次调用deepCopy方法,是的话创建一个weakmap实例来装遍历过程中出现过的对象
if(!map){
map = new WeakMap()
}
//判断传入的obj是否为对象
if( obj === null || typeof obj !== 'Object' ){
return obj
}
//如果map中已经存在这个对象说明出现了循环引用问题
if(map.get(obj)){
return obj
}
//map中没有就往map中存入该对象
map.set(obj,obj)
//根据obj的类型来给newObj创建初始值
let newObj = Array.isArray(obj) ? [] : {}
//遍历obj
for(let i in obj){
if(obj.hasOwnproperty(i)){ //判断当前属性是否为obj自身的属性
if(typeof obj[i] === 'Object'){ //判断当前属性是否为对象类型
newObj[i] = deepCopy(obj[i],map) //如果是对象类型就使用该方法进行递归处理
}else{
newObj[i] = obj[i] //不是对象类型就直接拷贝
}
}
}
return newObj //返回拷贝完成的newObj
}
对象的深拷贝2
export const deepClone = data => {
var type = getObjType(data)
var obj
if (type === 'array') {
obj = []
} else if (type === 'object') {
obj = {}
} else {
// 不再具有下一层次
return data
}
if (type === 'array') {
for (var i = 0, len = data.length; i < len; i++) {
obj.push(deepClone(data[i]))
}
} else if (type === 'object') {
for (var key in data) {
obj[key] = deepClone(data[key])
}
}
return obj
}
export const getObjType = obj => {
var toString = Object.prototype.toString
var map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object'
}
if (obj instanceof Element) {
return 'element'
}
return map[toString.call(obj)]
}
js中的数据类型:
- 基本数据类型:字符串(string)、数值(number)、布尔值(boolean)、null 、undefined、Symbol、Bigint
- 引用数据类型:对象数据类型(Object)、数组(Array)、函数数据类型(Function);
其中对象数据类型有包含:普通对象(Object),数组(Array),正则(Regexp),日期(Date),数学函数(Math)