拷贝
谈起拷贝,在我们实际的开发应用中非常多,对于基本类型而言,在变量进行赋值的时候,会自动为其开辟一个新的变量存储空间,而对于复杂数据类型而言(对象),赋值操作则需要思考更多;
对象拷贝方式
对象的拷贝分为深拷贝和浅拷贝,其主要的深和浅,主要是对象中包含的对象类型;如下所示,对obj的拷贝区分主要在offer中;
var obj = {
name:'mfy',
age:18,
offer:{
company:'~',
jobs:'sercert'
}
}
浅拷贝
浅拷贝主要解决对象中引用的问题; 浅拷贝是按位拷贝对象,他会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。
- 如果属性基本类型,拷贝的基本类型的值;
- 如果属性是内存地址(引用类型),拷贝的就是内存地址 因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员一次拷贝),即只复制对象空间而不复制资源。
var a = {
age:22,
name:'mfy',
data:{
bane:'33',
defa:'333,
}
}
//b对a进行浅拷贝后
var b = a;
1.Object.assign()
Object.assgin()方法可以把任意多个的原对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是Object.assgin()进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
var a ={
age:22,
data:{
name:'3333',
}
}
var b = Object.assign({},a)
b.data.name =3;
b.age =333
console.log(b)
console.log(a)
当我们修改通过Object.assgin()的方法创建的对象的内部属性值的时候,对象中的基本类型是新开辟的空间存储,而复杂的类型即对象类型,则还是一个直接引用的关系,修改b会影响到a
2.Object.create(obj)
首先我们要清楚的是Object.create()的方法使用
传参
- 只有一个参数的时候
var obj = Object.create({a:3})
console.log(obj)
var obj1 = Object.create(null)
console.log(obj1)
- 传递两个参数的时候
var obj2 = Object.create({a:3},{
foo: {
writable: true,
configurable: true,
value: 'hello'
},
})
其实通过我们传递一个参数的时候,引用类型所在的位置进行判断,在__proto__的属性上,非常类似于new Object进行创建
进行拷贝
var a = {
age: 22,
data: {
name: '3333',
}
}
var c = Object.create(a)
c.age = 666
c.data.name = 444
a.age = '18'
console.log('a', a)
console.log('c', c)
我们能够发现变量c中的__proto__所使用的对象的引用和a对象的引用是一个
- 修改c变量的data.name属性,变量a发生了变化
- 修改变量a中的age属性,变量c的
__proto__的age属性也跟着变化
3.展开运算符
在es6中新增的方法;
var o = { age: 333, data: { name: 3232 } }
var d = { ...o }
d.data.name = 'd修改名字'
console.log('o',o)
console.log('d',d)
4.数组拷贝 Array.prototype.concat()
是对数组中的对象进行的一个赋值;
var arr =[12,2323,{name:2}]
var arr2=arr.concat();
arr2[2].name =44
console.log(arr)
console.log(arr2)
Array.prototype.concat 中对于数组的拷贝,其实也是浅拷贝,数组中某个项的对象是存在引用关系的
5.Array.prototype.slice()
实现的原理和上面一致,都是复制基本属性,里面的对象引用还是在一起的 只是复制了一个数组,不会需求原数组中的数据,可以当成特殊的对象;
深拷贝
深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象; 当b进行引用赋值a的时候,会重新生成一个新的地址引用,不会和a共用一个地址;这样a/b互相改变值的时候不会互相影响;
1.JSON.parse(JSON.string(obj))
JSON.parse(JSON.string(obj)) 是我经常在工作用到的方法,当然也是区分情景的;
var obj = {
offer: {
money: 13133,
company: "一代大神"
},
name: 'mfy'
}
var obj2 = JSON.parse(JSON.stringify(obj));
obj2.offer.money = '修改成自定义的money'
console.log('obj',obj)
console.log('obj2',obj2)
😢 缺点
虽然JSON.parse(JSON.string(obj))给我们提供了很大的便捷度,但是也是存在缺点的,
无法序列化函数❗️
obj2中是不存在obj1中所包含的函数的
var obj = {
fun:function(){console.log("我是函数")},
name: 'mfy'
}
var obj2 = JSON.parse(JSON.stringify(obj));
console.log('obj',obj)
console.log('obj2',obj2)
😂 会忽略为undefined + 会忽略symbol
var a = Symbol('hh',33);
var obj = {
name: 'mfy',
age:undefined,
a,
}
var obj2 = JSON.parse(JSON.stringify(obj));
console.log('obj',obj)
console.log('obj2',obj2)
2.递归实现深拷贝
// 未做细化
function deepMerge(obj) {
//类型校验省略
var target = {};
for (var key in obj) {
let itemObj = obj[key];
if (typeof itemObj == 'object') {
target[key] = deepMerge(itemObj)
} else {
target[key] = obj[key]
}
}
return target;
}
var obj3 = {age:18,name:'mfy',offer:{company:'XXX',jobs:333}}
var obj4 = deepMerge(obj3);
obj4.offer.company="big company"
console.log('obj4',obj4)
console.log('obj3',obj3)
obj4经过深拷贝后,再次修改内部对象类型,不会影响到obj3,此时两个变量的存储方式,存储空间是无任何关联的
3.使用函数库lodash
在lodash库中也是提供了方法,去深拷贝,其原理基本和2一致
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);
总结
| 操作 | 第一层基本数据类型改变 | 第二层为引用类型改变 |
|---|---|---|
| 赋值 | 原数据会改变 | 原数据会改变 |
| 浅拷贝 | 原数据不会改变 | 原数据会改变 |
| 深拷贝 | 原数据不会改变 | 原数据不会改变 |
参考文档
文章是好久之前进行总结的,相关参考资料由于当时没标注,所以无法找到链接了,希望理解;😂