文章目录
什么是深拷贝?
深浅拷贝都是对于复杂数据类型Object
来说的,而对象涉及到传值传址的问题
下列浅拷贝
let obj1 = {
a: 1,
b: 2,
}
let obj2 = obj1
浅拷贝就是单纯的传值,obj1
对象的地址值赋值给obj2
这就会导致obj1,obj2操作的是同一个对象
深拷贝就是在堆内存中重新开辟一块地址空间,实现两者互不影响的拷贝
方法一:JSON序列化与反序列化
var obj = {
a: 1,
b: [1,2,3],
c: {
d: 4,
e: 5,
},
}
var obj2 = JSON.parse(JSON.stringify(obj))
obj2.a = 2;
obj2.c.e = 555;
console.log(obj, obj2)
缺点
-
不支持function
-
不支持undefined
-
不支持引用,环状结构
var obj = { name: 'lc' } obj.self = obj; var obj2 = JSON.parse(JSON.stringify(obj)) console.log(obj, obj2)
- 不支持正则表达式
- 不支持正则表达式
JSON不支持的数据类型都无法拷贝
JSON只支持:string, boolean, array, object, null五种类型
方法二:递归克隆
思路
递归
- 看结点的类型(7种)number,string,bool,undefined,null,object,symbol(arry以及function是object的子类型)
- 如果是基本类型就直接赋值
- 如果是object就分情况讨论
object
- 普通 object:
for in
for in 有个问题,他会遍历原型里的键 - 数组 Array初始化
- 函数,闭包如何拷贝?
- 日期Date 如何拷贝?
实现
1 拷贝普通类型
function deepClone(source) {
return source;
}
2 拷贝对象object
以及数组类型
function deepClone(source) {
if (source instanceof Object) {
// 判断是对象还是子类型数组,分别处理返回结果的类型
const dist = source instanceof Array ? new Array() : new Object()
for (let key in source) { // for in 会遍历source原型上的属性
dist[key] = deepClone(source[key]);
}
return dist
}
return source;
}
3 拷贝函数function
function deepClone(source) {
if (source instanceof Object) {
let dist = {};
if (source instanceof Array) {
dist = [];
} else if (source instanceof Function) {
dist = function () {
return source.apply(this, arguments);
}
}
for (let key in source) { // for in 会遍历source原型上的属性
dist[key] = deepClone(source[key]);
}
return dist
}
return source;
}
let source = {
deep: 1,
common: '普通数据类型',
arr: [1, 2, 3],
obj: {
deep: 2,
obj: {
deep: 3,
}
},
func: function (x, y) { return x + y },
}
let source2 = deepClone(source);
// console.log(source)
// console.log(source2)
console.log(source.func(1, 2))
console.log(source2.func(1, 2))
4. 解决闭环问题
闭环问题
以上对象总会有一个结尾,递归会终止
但是如果对象有一个环,就会死循环
比如说window对象其实就是一个环
let source = {
circle: {},
}
source.circle.self = source.circle;
let source2 = deepClone(source);
解决
记录已经遍历过的对象,每次深拷贝之前判断是否走过就行了
如果已经是拷贝过的,注意要返回的是之前已经拷贝过的对象
这样才能实现独立的新环
所以保存原对象时要同时保存拷贝对象
let cache = []; // 保存已经克隆的对象
function findCache(source) {
for (let i = 0; i < cache.length; i++) {
if (cache[i][0] === source) {
// 返回之前的拷贝
return cache[i][1];
}
}
}
function deepClone(source) {
if (source instanceof Object) {
let cacheDist = findCache(source);
// 判断是否拷贝过
if (cacheDist) {
return cacheDist;
}
let dist = {}; // 默认为对象
if (source instanceof Array) {
dist = [];
} else if (source instanceof Function) {
dist = function () {
return source.apply(this, arguments);
}
}
cache.push([source, dist]);
for (let key in source) { // for in 会遍历source原型上的属性
dist[key] = deepClone(source[key]);
}
return dist;
}
return source;
}
circle = {
a: 1
}
circle.self = circle;
let circle2 = deepClone(circle);
circle2.a = 1111111;
console.log(circle.self)
console.log(circle2.self)
console.log(circle !== circle2)
console.log(circle.a === circle2.a)
console.log(circle.self !== circle2.self)
5 解决爆栈问题(一般不考虑)
如果一个对象里有很多对象,递归会调用栈,如果栈的长度高于2万,就会出现爆栈问题
通过for循环模拟一个无限级对象
let a = {
child: null,
}
let b = a;
for (let i = 0; i < 20000; i++) {
b.child = {
child: null
}
b = b.child;
}
解决方法
将竖向的递归改为横向的队列,依次深拷贝,一般来说不用考虑爆栈
6 拷贝正则表达式和Date()
正则表达式
有两个重要属性
只需要获取这两个属性重新声明正则就行了
else if (source instanceof RegExp) {
dist = new RegExp(source.source, source.flags)
}
拷贝Date()
else if (source instanceof Date) {
dist = new Date(source);
}
由此可得
如果对象是一些特殊子对象
只能else if 来判断,并new一个新的这个特殊对象
完整代码
let cache = []; // 保存已经克隆的对象
function findCache(source) {
for (let i = 0; i < cache.length; i++) {
if (cache[i][0] === source) {
// 返回之前的拷贝
return cache[i][1];
}
}
}
function deepClone(source) {
if (source instanceof Object) {
let cacheDist = findCache(source);
// 判断是否拷贝过
if (cacheDist) {
return cacheDist;
}
let dist = {}; // 默认为对象
if (source instanceof Array) {
dist = [];
} else if (source instanceof Function) {
dist = function () {
return source.apply(this, arguments);
}
} else if (source instanceof RegExp) {
dist = new RegExp(source.source, source.flags)
} else if (source instanceof Date) {
dist = new Date(source);
}
cache.push([source, dist]);
for (let key in source) { // for in 会遍历source原型上的属性
// 如果不是本身的属性就跳过
if (!source.hasOwnProperty(key)) continue;
dist[key] = deepClone(source[key]);
}
return dist;
}
return source;
}
其他问题(面试技巧)
可以留下这些问题给面试官,并且想好解决方案
1 是否需要拷贝原型属性?
- 不需要,内存太大
- 所以需要跳过原型属性
- 解决for in 遍历原型的问题
给源对象一个原型属性
let a = Object.create({ name: 'a' });
a.xxx = { yyy: { zzz: 1 } };
let b = deepClone(a);
console.log(a.__proto__)
console.log(b.__proto__)
深拷贝的遍历中,不是自身属性就跳过
if (!source.hasOwnProperty(key)) continue;
2 解决目前cache的问题
cache在进行一次深拷贝后没有清空,会造成全局变量污染问题
面向对象来解决,封装成一个class
class Cloner {
constructor() {
this.cache = []; // 保存已经克隆的对象
}
findCache(source) {
for (let i = 0; i < this.cache.length; i++) {
if (this.cache[i][0] === source) {
// 返回之前的拷贝
return this.cache[i][1];
}
}
}
deepClone(source) {
if (source instanceof Object) {
let cacheDist = this.findCache(source);
// 判断是否拷贝过
if (cacheDist) {
return cacheDist;
}
let dist = {}; // 默认为对象
if (source instanceof Array) {
dist = [];
} else if (source instanceof Function) {
dist = function () {
return source.apply(this, arguments);
}
} else if (source instanceof RegExp) {
dist = new RegExp(source.source, source.flags)
} else if (source instanceof Date) {
dist = new Date(source);
}
this.cache.push([source, dist]);
for (let key in source) { // for in 会遍历source原型上的属性
// 如果不是本身的属性就跳过
if (!source.hasOwnProperty(key)) continue;
dist[key] = this.deepClone(source[key]);
}
return dist;
}
return source;
}
}
let obj1 = {
a:1,
obj: {
a: 1,
},
func: function(){},
}
let dc = new Cloner();
let obj2 = dc.deepClone(obj1);
obj2.a = 111111;
obj2.obj.a = 2222222;
obj2.func = function(){console.log('成功')};
console.log(obj1)
console.log(obj2)
obj2.func();