我们都知道,在JS中对象属于引用类型
,在内存中是以地址的形式
保存数据。那么当我们需要复制/克隆一个对象
的时候,克隆的仅是内存地址
,这个地址实际指向的才是真正的数据。改变两个对象中的任何一个,另一个对象也会跟着改变。其实这种问题在实际项目中也是经常出现的。实际项目中可能需要操作一个克隆的新对象而非老对象
,我一般习惯直接用JSON.parse(JSON.stringfy())
来克隆一个对象。这样做属于深克隆
,就是复制出来的对象和原对象没有任何关系。虽然简单,但是这种方式也有缺点,有什么缺点呢,一起来探究下JS中的深浅克隆
。
浅克隆
对于一个多维的对象
只克隆了第一维度
比如说现在有这样一个
对象obj
:
let obj = {
a: 100,
b: [10, 20, 30],
c: {
x: 10
},
d: /^\d+$/,
e: new Date(),
f: function () {
console.log('fff')
}
};
浅克隆方法1
我们写一个浅克隆的方法 cloneObj
// 方法1:
function cloneObj(obj){
let obj2 = {};
for(let key in obj){
// 循环原对象中的每个key
if(!obj.hasOwnProperty(key)) break;
obj2[key] = obj[key];
}
return obj2;
}
hasOwnProperty()
方法会去查找obj上是否有某属性,返回的是布尔值
但是不会去原型链上去查找
。上面for循环中就是通过该原理来中断循环
可以测试一下该方法是否可用
let result = cloneObj(obj);
console.log(result);
obj.a = 200;
console.log(result);
打印结果:
{
a: 100,
b: [ 10, 20, 30 ],
c: { x: 10 },
d: /^\d+$/,
e: 2020-02-18T12:03:49.133Z,
f: [Function: f]
}
{
a: 100,
b: [ 10, 20, 30 ],
c: { x: 10 },
d: /^\d+$/,
e: 2020-02-18T12:03:49.133Z,
f: [Function: f]
}
可见,新复制的对象属性值
并没有因为旧对象属性值的改变而改变
。貌似没有问题,那如果改变旧对象的第二层属性
呢?
obj.c.x = 100000;
console.log(result);
打印结果:
{
a: 100,
b: [ 10, 20, 30 ],
c: { x: 100000 }, // c 也会跟着改变
d: /^\d+$/,
e: 2020-02-18T12:03:49.133Z,
f: [Function: f]
}
总结 该方法可以实现克隆
,但是只能实现浅克隆
。
除了这个方法我们该可以通过以下更简单方式来实现浅克隆:
浅克隆方法2
展开运算符
// 方法2
let new_obj = { ...obj }
浅克隆方法3
Object.assign()
Object.assign
方法用于对象的合并
,将源对象(source
)的所有可枚举属性
,复制到目标对象(target)
// 方法3
let new_obj = Object.assign({},obj);
深克隆
知道了浅克隆
那么对应的深克隆
自然而然也就知道深克隆
就是对于一个多维的对象
克隆了多个维度
深克隆方法1
JSON.parse(JSON.stringfy())
相信这是很多人在实际项目中用到的深克隆方式
let new_obj = JSON.parse(JSON.stringify(obj));
测试一下该方法是否可用
console.log(new_obj);
obj.a = 200;
obj.c.x = 100000;
console.log(new_obj);
打印结果:
{
a: 100,
b: [ 10, 20, 30 ],
c: { x: 10 },
d: {},
e: '2020-02-19T06:17:56.741Z'
}
{
a: 100,
b: [ 10, 20, 30 ],
c: { x: 10 },
d: {},
e: '2020-02-19T06:17:56.741Z'
}
可以发现 不管是改变原数组中一维属性obj.a = 200
还是二维属性obj.c.x = 100000
,新数组的属性都没有跟着改变。但是同时也发现了一个问题:一些属性
在克隆过程中消失/变化
了。
1 - 最明显的是函数类型
在克隆过程中消失了
2 - 原对象中的正则
在新对象中变成了一个空对象
3 - 而且时间类型
数据被转成了一个字符串类型
,而不是一个时间对象
所以说该方法在对于函数 / 正则 / 时间
类型的克隆都有问题。
对于原对象中只包含一般属性
的克隆 我们可以使用JSON.parse(JSON.stringfy())
方法来实现
深克隆方法2
写一个递归方法来实现深克隆
function deepClone(obj){
// null情况
if(obj === null) return null;
// 基本类型
if(typeof obj !== 'object') return obj;
// 函数类型
if(obj instanceof Function) return new Function(obj);
// 正则类型
if(obj instanceof RegExp) return new RegExp(obj);
// 时间类型
if(obj instanceof Date) return new Date(obj);
// 通过原对象obj的构造函数再new一个新的实例
let new_obj = new obj.constructor();
// 遍历
for(let key in obj){
// 只克隆原对象obj的私有属性 不克隆原对象obj的原型链上的属性
if(obj.hasOwnProperty(key)){
new_obj[key] = deepClone(obj[key])
}
}
return new_obj
}
测试一下该方法是否可用
let result = deepClone(obj);
console.log(result);
obj.a = 200;
obj.c.x = 100000;
console.log(result);
打印结果:
{
a: 100,
b: [ 10, 20, 30 ],
c: { x: 10 },
d: /^\d+$/,
e: 2020-02-19T06:37:55.756Z,
f: [Function: f]
}
{
a: 100,
b: [ 10, 20, 30 ],
c: { x: 10 },
d: /^\d+$/,
e: 2020-02-19T06:37:55.756Z,
f: [Function: f]
}
即可以看到,不管是函数类型 正则类型 还是时间类型
都得到了正确的深克隆
,而且更改原对象
的不管是几维属性
,新对象
都不会受到影响。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。