js深拷贝/浅拷贝
数据类型
数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和对象数据类型。
- 基本数据类型的特点:直接存储在栈(stack)中的数据
- 引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
浅拷贝与深拷贝
深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。
深拷贝和浅拷贝的示意图大致如下:
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
赋值和浅拷贝的区别
赋值:复制引用类型存在栈中的地址
浅拷贝:新建一个引用类型,把原数据的存在栈里的属性值复制到新数据
深拷贝:新建一个引用类型,把原数据的存在堆里的属性值复制到新数据
浅拷贝的实现方式
//原始数据 var obj = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3] }; var arr = [1, "hello", { f: { g: 1 } }]; // 检查数据类型 function checkType(target) { return Object.prototype.toString.call(target).slice(8, -1); }
1.Object.assign()
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign()进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
var obj1 = Object.assign({},obj); obj1.a = 222; console.log(obj.b.f === obj1.b.f, "Object.assign"); // true console.log(obj.a === obj1.a, "Object.assign"); // false
2.Array.prototype.concat()
var arr1 = arr.concat(); console.log(arr[2].f === arr1[2].f, "Array.prototype.concat"); //true
3.Array.prototype.slice()
var arr2 = arr.slice(); console.log(arr[2].f === arr2[2].f, "Array.prototype.slice"); //true
4.赋值
function shallowCopy(target) { let result = {}; for (let i in target) { if (target.hasOwnProperty(i)) { result[i] = target[i]; } } return result; } var obj2 = shallowCopy(obj); console.log(obj.b.f === obj2.b.f, "hasOwnProperty"); // true
根据四种浅拷贝方式,抽象出一个浅拷贝方法
// 浅拷贝 function shallowClone(target) { let result; let targetType = checkType(target); // 判断传入数据类型 if (targetType === "Object") { result = Object.assign(target); } else if (targetType === "Array") { result = target.concat(); } else { return target; } return result; } var obj4 = shallowClone(obj); var arr4 = shallowClone(arr); console.log(obj.b.f === obj4.b.f, "shallowClone"); // true console.log(arr[2].f === arr4[2].f, "shallowClone"); // true
深拷贝的实现方式
1.递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝
function deepClone(target) { let result; let targetType = checkType(target); // 判断传入数据类型 if (targetType === "Object") { result = {}; } else if (targetType === "Array") { result = []; } else { return target; } // 遍历传入目标数据 for (let i in target) { let value = target[i]; if (checkType(value) === "Object" || checkType(value) === "Array") { result[i] = deepClone(value); } else { result[i] = value; } } return result; } var obj3 = deepClone(obj); var arr3 = deepClone(arr); console.log(obj.b.f === obj3.b.f, "deepClone"); // false console.log(arr[2].f === arr3[2].f, "deepClone"); // false
2.JSON.parse(JSON.stringify())
var obj5 = JSON.parse(JSON.stringify(obj)); var arr5 = JSON.parse(JSON.stringify(arr)); obj5.d() console.log(obj.b.f === obj5.b.f, "deepClone"); // false console.log(arr[2].f === arr5[2].f, "deepClone"); // false
注意:这种方式不可以用于数据里面有函数作为值的数据,因为JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,不能接受函数