js中的浅拷贝和深拷贝
js的数据类型: 基本数据类型:String、Number、Boolean、Null、Undefined; 引用数据类型:Object(Array、Date、Function、RegExp)。
基本数据类型和引用数据类型的区别: 基本数据类型:存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配,是直接按值存放的,所以可以直接访问。 引用数据类型:引用类型(object)是存放在堆内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况来进行特定的分配。 引用数据类型保存在堆内存中,然后在栈内存中保存了一个对堆内存中实际对象的引用,即数据在堆内存中的地址,JS对引用数据类型的操作都是操作对象的引用而不是实际的对象,如果obj1拷贝了obj2,那么这两个引用数据类型就指向了同一个堆内存对象,具体操作是obj1将栈内存的引用地址复制了一份给obj2,因而它们共同指向了一个堆内存对象;
例如: var a = 1; b = a; // 栈内存会开辟一个新的内存空间,此时b和a都是相互独立的 b = 2; console.log(a); // 1
例如: var arr = [1, 2, 3, 4]; arr1 = arr; //arr将栈内存的引用地址复制了一份给arr1,因而它们共同指向了一个堆内存对象; arr1[2] = 9; console.log(arr); //[1,2,9,4]
基本数据类型值不可变: javascript中的原始值(undefined、null、布尔值、数字和字符串)与对象(包括数组和函数)有着根本区别。 原始值是不可更改的:任何方法都无法更改(或"突变")一个原始值。 对数字和布尔值来说显然如此 —— 改变数字的值本身就说不通,而对字符串来说就不那么明显了,因为字符串看起来像由字符组成的数组,我们期望可以通过指定索引来假改字符串中的字符。 实际上,javascript 是禁止这样做的。字符串中所有的方法看上去返回了一个修改后的字符串,实际上返回的是一个新的字符串值。
基本数据类型的值是不可变的,动态修改了基本数据类型的值,它的原始值也是不会改变的, var str = "abc"; str[1] = "f"; console.log(str); //abc
有很多操作字符串的方法,但是这些方法都是返回一个新的字符串,并没有改变其原有的数据。
引用类型值可变 var a = [1, 2, 3]; a[1] = 5; console.log(a[1]); // 5
浅拷贝 与 深拷贝: 浅拷贝和深拷贝都只针对于引用数据类型,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存;但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象;浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制。
区分深拷贝与浅拷贝,就是假设B复制了A,当修改B时,看A是否会发生变化,如果A也跟着变了,说明这是浅拷贝,拿人手短,如果A没变,那就是深拷贝,自食其力。
浅拷贝:
for···in只循环第一层 function simpleCopy(obj1) { var obj2 = Array.isArray(obj1) ? [] : {}; for (let i in obj1) { obj2[i] = obj1[i]; } return obj2; } var obj1 = { a: 1, b: 2, c: { d: 3 } } var obj2 = simpleCopy(obj1); obj2.a = 3; obj2.c.d = 4; console.log(obj1.a); // 1 console.log(obj2.a); // 3 console.log(obj1.c.d); // 4 console.log(obj2.c.d); // 4
Object.assign()实现浅拷贝及一层的深拷贝 var obj1 = { a: { b: 1 }, c: 2 } var obj2 = Object.assign({}, obj1) obj2.a.b = 3; obj2.c = 3 console.log(obj1.a.b); // 3 console.log(obj2.a.b); // 3 console.log(obj1.c); // 2 console.log(obj2.c); // 3
Object实现拷贝2,浅拷贝 var obj1 = { a: 1, b: 2, c: { d: 3 } } var obj2 = Object.create(obj1); obj2.a = 3; obj2.c.d = 4; console.log(obj1.a); // 1 console.log(obj2.a); // 3 console.log(obj1.c.d); // 4 console.log(obj2.c.d); // 4
深拷贝
1、递归实现深拷贝 function deepCopy(obj1) { var obj2 = Array.isArray(obj1) ? [] : {}; if (obj1 && typeof obj1 === "object") { for (var i in obj1) { var prop = obj1[i]; // 避免相互引用造成死循环,如obj1.a=obj if (prop == obj1) { continue; } if (obj1.hasOwnProperty(i)) { // 如果子属性为引用数据类型,递归复制 if (prop && typeof prop === "object") { obj2[i] = (prop.constructor === Array) ? [] : {}; arguments.callee(prop, obj2[i]); // 递归调用 } else { // 如果是基本数据类型,只是简单的复制 obj2[i] = prop; } } } } return obj2; } var obj1 = { a: 1, b: 2, c: { d: 3 } } var obj2 = deepCopy(obj1); obj2.a = 3; obj2.c.d = 4; console.log(obj1.a); // 1 console.log(obj2.a); // 3 console.log(obj1.c.d); // 3 console.log(obj2.c.d); // 4
2、Object.create实现深拷贝1,但也只能拷贝一层 function deepCopy(obj1) { var obj2 = Array.isArray(obj1) ? [] : {}; if (obj1 && typeof obj1 === "object") { for (var i in obj1) { var prop = obj1[i]; // 避免相互引用造成死循环,如obj1.a=obj if (prop == obj1) { continue; } if (obj1.hasOwnProperty(i)) { // 如果子属性为引用数据类型,递归复制 if (prop && typeof prop === "object") { obj2[i] = (prop.constructor === Array) ? [] : Object.create(prop); } else { // 如果是基本数据类型,只是简单的复制 obj2[i] = prop; } } } } return obj2; } var obj1 = { a: 1, b: 2, c: { d: 3 } } var obj2 = deepCopy(obj1); obj2.a = 3; obj2.c.d = 4; console.log(obj1.a); // 1 console.log(obj2.a); // 3 console.log(obj1.c.d); // 3 console.log(obj2.c.d); // 4
3、使用JSON.stringify和JSON.parse实现深拷贝 JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。 缺点: (1)如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式,而不是时间对象; (2)如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象; (3)如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失; (4)如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null; (5)JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor; (6)如果对象中存在循环引用的情况也无法正确实现深拷贝;
以上,如果拷贝的对象不涉及上面讲的情况,可以使用JSON.parse(JSON.stringify(obj))实现深拷贝,但是涉及到上面的情况(除循环引用的情况外),可以考虑使用如下方法实现深拷贝。 function deepClone(data) { const type = this.judgeType(data); let obj; if (type === "array") { obj = []; } else if (type === "object") { obj = {}; } else { // 不再具有下一层次 return data; } if (type === "array") { // eslint-disable-next-line for (let i = 0, len = data.length; i < len; i++) { obj.push(this.deepClone(data[i])); } } else if (type === "object") { // 对原型上的方法也拷贝了.... // eslint-disable-next-line for (const key in data) { obj[key] = this.deepClone(data[key]); } } return obj; } function judgeType(obj) { // tostring会返回对应不同的标签的构造函数 const toString = Object.prototype.toString; const map = { "[object Boolean]": "boolean", "[object Number]": "number", "[object String]": "string", "[object Function]": "function", "[object Array]": "array", "[object Date]": "date", "[object RegExp]": "regExp", "[object Undefined]": "undefined", "[object Null]": "null", "[object Object]": "object", }; if (obj instanceof Element) { return "element"; } return map[toString.call(obj)]; }
4、如果被拷贝中没有对时间、正则要求兼容,可以采用如下方法 function deepClone(obj){ let objClone = Array.isArray(obj)?[]:{}; if(obj && typeof obj==="object"){ for(key in obj){ if(obj.hasOwnProperty(key)){ //判断ojb子元素是否为对象,如果是,递归复制 if(obj[key]&&typeof obj[key] ==="object"){ objClone[key] = deepClone(obj[key]); }else{ //如果不是,简单复制 objClone[key] = obj[key]; } } } } return objClone; }
5、热门的函数库lodash,也有提供_.cloneDeep用来做深拷贝
6、jquery实现深拷贝,jquery 提供一个$.extend可以用来做深拷贝 var $ = require("jquery"); var obj1 = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3] }; var obj2 = $.extend(true, {}, obj1); console.log(obj1.b.f === obj2.b.f); // false