1、前情概要
1.1、基本数据类型
Number、String、Boolean、Null、Undefined、Symbol、BigInt。基本数据类型是直接存储在栈中的数据。
1.2、 引用数据类型
Object、Array、Function、Date、RegExp、Map、Set、WeekMap、WeekSet、Promise、Error、Buffer。引用数据类型栈中存储的是该对象在栈中的引用(地址),真实的数据存储在堆中。
2、浅拷贝和深拷贝
- 浅拷贝:拷贝一层,创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
- 深拷贝:将一个对象内存中完整的拷贝一份出来,从堆内存中开辟一个新的内存存放新对象,修改一个对象并不会影响原对象。
3、赋值、深拷贝、浅拷贝的区别
前提是针对引用类型
- 赋值:当把一个对象赋值给一个新的变量时,赋值的其实是该对象在栈中的地址,而不是堆中的数据。两个对象指向的是同一个存储空间,无论哪个对象发生改变,都是改变同一个存储空间的所有内容,两个对象联动。
- 浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一个内存,会相互影响。
- 深拷贝:从堆内存中开辟一个新的区域存放新对象,堆对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。
赋值例子:
let objOne = {
age:20,
name:["张三","李四"]
}
let objTwo = objOne;
objTow.age = 18;
obj.name[0]="王二";
console.log(objOne); // {age:18,name:["王二","李四"]}
console.log(objTwo); // {age:18,name:["王二","李四"]}
浅拷贝例子:
let objOne = {
age:20,
name:["张三","李四"]
}
let objThree = shallowClone(objOne);
objThree.age = 18;
obj.objThree[0]="王二";
console.log(objOne); // {age:20,name:["王二","李四"]}
console.log(objThree); // {age:18,name:["王二","李四"]}
// 这是个浅拷贝的方法
function shallowClone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
深拷贝例子:
let objOne = {
age:20,
name:["张三","李四"]
}
let objFour = deepClone(objOne);
objFour.age = 18;
objFour.name[0]="王二";
console.log(objOne); // {age:20,name:["张三","李四"]}
console.log(objFour); // {age:18,name:["王二","李四"]}
// 这是个深拷贝的方法
function deepClone(obj) {
if (obj === null) return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (typeof obj !== "object") return obj;
let cloneObj = new obj.constructor();
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key]);
}
}
return cloneObj;
}
原数据:objOne {age:20,name:["张三","李四"]}
赋值操作之后的:
原数据:bjTwo {age:18,name:["王二","李四"]}
赋值数据:objTwo {age:18,name:["王二","李四"]}
浅拷贝之后的:
原数据:objOne {age:20,name:["王二","李四"]}
浅拷贝数据:objThree {age:18,name:["王二","李四"]}
深拷贝之后的:
原数据:objOne {age:20,name:["张三","李四"]}
深拷贝数据:objFour {age:18,name:["王二","李四"]}
和原数据是否指向同一对象 | 第一层数据为基本数据类型 | 原数据中包含的子对象 | |
赋值 | 是 | 改变会使原数据一同改变 | 改变会使原数据一同改变 |
浅拷贝 | 否 | 改变不会使原数据一同改变 | 改变会使原数据一同改变 |
深拷贝 | 否 | 改变不会使原数据一同改变 | 改变不会使原数据一同改变 |
4、浅拷贝的实现方式
-
Object.assign()
-
函数库lodash的_.clone方法
-
展开运算符...
-
Array.prototype.concat()
-
Array.prototype.slice()
5、深拷贝的实现方式
-
JSON.parse(JSON.stringify())
-
函数库lodash的_.cloneDeep方法
-
jQuery.extend()方法
-
手写递归方法
原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是很拷贝
对象存在循环引用的情况,即对象的属性直接的引用了自身的情况;解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝。
关于这块学习,请仔细阅读ConardLi大佬
如何写出一个惊艳面试官的深拷贝?这篇文章。
下面代码为只考虑到循环引用的情况:
function clone(target, map = new Map()) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
for (const key in target) {
cloneTarget[key] = clone(target[key], map);
}
return cloneTarget;
} else {
return target;
}
};
大佬写的更为牛的代码:
还在学习中...
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);
标签:obj,复习,对象,age,JavaScript,return,拷贝,name
From: https://blog.csdn.net/qq_52050536/article/details/137152265