前言: 对于拷贝需要考虑数据的类型
数据类型分为:基本数据类型和引用类型;
基本数据类型是存储在栈中的简单数据;常见:String,Number,Undefined,Null,Boolean以及Es6新定义的Symbol;
引用类型值是引用类型实例,它是保存在堆中的一个对象,引用类型是一中数据结构;常见:Array,Object,Function,Date,RegExp等,Es6提供的set和map新的数据结构;
复制时的区别:
基本数据类型是按值访问的;
引用类型复制时,是复制的存在栈中的指针,指针指向堆内的数据,如果只是简单的赋值,就只是赋值了栈中的指针,通过一个变量操作堆内的对象会影响两者;
一、浅拷贝
理解:创建一个新对象,这个对象有这原始对象属性值的一份精确拷贝;如果是基本类型,则拷贝的是基本类型的值,如果是引用类型,则拷贝的是内存地址,所以一个对象改变了这个地址对应点堆内数据,则会影响到另外一个对象
1:Object.assign(target,...source)
# ES6提供的浅拷贝的方法,接受的第一个参数是拷贝的目标,第二个参数将要拷贝的源对象
# Object.assign()只是拷贝根属性上的值(对象的第一层属性),如果根属性上的值是引用类型的数据,则仍是浅拷贝
# 特点: 不会拷贝继承的属性;可以拷贝symbol类型的值;
# 可以理解为:Object.assign()只是简单的=赋值,遍历从右到左遍历source上的属性赋值到target目标对象上
2:扩展运算符 - 浅拷贝
var cloneObj = { ...obj } // 对象
var cloneArr = [ ...arr ] // 数组
# 对于值是对象的属性无法完全拷贝成两个完全不相同的对象;但是如果对象的值都是基本数据类型的话,扩展运算符还是比较方便的
3:Array.property.slice() - arr.slice(begin, end)
返回一个新的数组对象,这一对象是由begin和end决定的原数组的浅拷贝;原数组不改变;
4:concat():该方法是连接两个或多个数组,但它不会修改原先的数组;问题:只会对第一层进行拷贝,对深层的不起作用
二、深拷贝
理解:将一个对象在内存中完整的拷贝出来,并内存中开辟一个新的空间存放新对象,这两个对象是相互独立的,且修改新对象不会影响原先的对象
1:JSON.stringify() JSON.parse()
原理:将对象序列化成JSON字符串,将对象的内容转化成字符串的形式再保存到磁盘上,再用JSON.parse()反序列化将JSON字符串转换成新的对象
注意:拷贝的对象的值有函数、undefined、symbol则经过反序列化后的JSON字符串中的键值对会消失;对象中包括NaN、infinity则序列化的后变为null
// JSON var obj = { name: 'zs', arr : [ '1', 'a', { msg: '打贝贝' }] } // 针对对象深拷贝 var newObj = {} // 便利对象里的键进行JSON编码和解析 for (var k in obj) { newObj = JSON.parse(JSON.stringfiy(obj[k])) } // 该方法对对象里的值是基本数据类型是可以的,对于引用类型是不成立的
2:递归
// 2:递归深拷贝 var obj = { name: "贝贝", age: 20, arr: [ '1', 'a', { msg: '揍贝贝!' }, obj: { console.log('内层的对象') } } function deepClone(obj) { // 判断obj是数组还是对象,响应的格式进行拷贝 const cloneObj = Array.isArray(obj) ? [] : {} // 拷贝对象不能为空且是对象 if (obj && typeOf obj === 'Object') { for (var k in obj) { // 判断对象里是否有键 if (obj.hasOwnProperty(k)) { if (obj[k]&& typeOf obj[k] === 'Object') { cloneObj[k] = deepClone(obj) } else { cloneObj[k] = obj[k] } } } } return cloneObj }
// 该方法只是针对object引用类型的值进行循环迭代
3:lodash和jquery都有已经封装好的深拷贝的方法,有兴趣的可以深入研究下