为什么有深复制、浅复制?
一、为什么有深拷贝和浅拷贝?
JavaScript中有两种数据类型,基本数据类型如undefined、null、boolean、number、string,另一类是Object。简单数据类型只存储在内存中的栈区,复制的时候是值传递给新的索引。而复杂数据类型由栈区和堆区共同储存,栈区执行同样的操作,只是把堆地址复制了一份,而真实数据在堆区中依然只有一份。为了不影响原有数据,那么我们就新建一个对象,遍历原有对象的属性赋值到新属性。
这个要从js中的数据类型说起,js中数据类型分为基本数据类型和引用数据类型。
let newObj = {}for (let prop in obj) { newObj[prop] = obj[prop]}
基本类型值指的是那些保存在栈内存中的简单数据段,即这种值是完全保存在内存中的一个位置。包含Number,String,Boolean,Null,Undefined ,Symbol。
上面这个循环也可以用Object.assign({}, obj);来实现。这样做是否解决问题?未必,因为Object中可以嵌套Object,如果原有对象属性中有复杂数据类型,那么新的对象中也只能得到一个地址。这种情况被称为浅复制。我们希望能将对象中的对象,无论多少层,都能复制一份,能达到这种效果的,称为深复制。
引用类型值指的是那些保存在堆内存中的对象,所以引用类型的值保存的是一个指针,这个指针指向存储在堆中的一个对象。除了上面的 6 种基本数据类型外,剩下的就是引用类型了,统称为 Object 类型。细分的话,有:Object 类型、Array 类型、Date 类型、RegExp 类型、Function 类型 等。
深复制的几种方法
正因为引用类型的这种机制, 当我们从一个变量向另一个变量复制引用类型的值时,实际上是将这个引用类型在栈内存中的引用地址复制了一份给新的变量,其实就是一个指针。因此当操作结束后,这两个变量实际上指向的是同一个在堆内存中的对象,改变其中任意一个对象,另一个对象也会跟着改变。
首先假设有数据
因此深拷贝和浅拷贝只发生在引用类型中。简单来说他们的区别在于:
let obj = { a: 23, b: [0, 1, [2, 3], function() {console.log('in array')}, undefined], c: {k: 'value'}, d: function() {console.log('a')} }
10bet,浅拷贝 只会将对象的各个属性进行依次复制,并不会进行递归复制,也就是说只会赋值目标对象的第一层属性。 深拷贝不同于浅拷贝,它不只拷贝目标对象的第一层属性,而是递归拷贝目标对象的所有属性。 浅拷贝 对于目标对象第一层为基本数据类型的数据,就是直接赋值,即「传值」;而对于目标对象第一层为引用数据类型的数据,就是直接赋存于栈内存中的堆内存地址,即「传址」,并没有开辟新的栈,也就是复制的结果是两个对象指向同一个地址,修改其中一个对象的属性,则另一个对象的属性也会改变, 深拷贝 而深复制则是开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。
JSON.parse(JSON.stringify(obj))
二、浅拷贝
let newObj = JSON.parse(JSON.stringify(obj))newObj.newKey = 'newValue'console.log(obj)console.log(newObj)
以下是实现浅拷贝的几种实现方式:
如果处理对象只是简单的键值对,这个方法效果不错。
1.Array.concat()
这个方法的缺点无法复制函数忽略undefined值无法处理Set、Map、Symbol类型原有的原型链会消失
const arr = [1,2,3,4,[5,6]]; const copy = arr.concat创建arr的副本 \改变基本类型值,不会改变原数组 copy[0] = 2; arr; //[1,2,3,4,[5,6]]; \改变数组中的引用类型值,原数组也会跟着改变 copy[4][1] = 7; arr; //[1,2,3,4,[5,7]];
循环引用的对象会报错
能实现类似效果的还有slice等,大家可以自己尝试一下~
递归法
2.Object.assign()
因为要处理属性的值也是Object这种情况,自然可以想到递归这种处理方法。
const obj1 = {x: 1, y: 2};const obj2 = Object.assign;obj2.x = 2; \修改obj2.x,改变对象中的基本类型值console.log //{x: 1, y: 2} //原对象未改变console.log //{x: 2, y: 2}
const obj1 = { x: 1, y: { m: 1 }};const obj2 = Object.assign;obj2.y.m = 2; \修改obj2.y.m,改变对象中的引用类型值console.log //{x: 1, y: {m: 2}} 原对象也被改变console.log //{x: 2, y: {m: 2}}
function deepCopy(oldObj, newObj) { let obj = newObj || {} for (let i in oldObj) { if (oldObj[i] === obj) { // 防止循环引用 continue } if (typeof oldObj[i] === 'object') { // obj[i] = (oldObj[i].constructor === Array) ? [] : {} obj[i] = oldObj[i] instanceof Array ? [] : {} deepCopy(oldObj[i], obj[i]) } else { obj[i] = oldObj[i] } } return obj;}
三、深拷贝
这样就能处理一个嵌套了Object和Array等复杂变量的对象。但是对象中还可能包含Date和RegExp对象、Set、Map……
1.JSON.parse
Lodash库
const obj1 = { x: 1, y: { m: 1 }};const obj2 = JSON.parse;console.log //{x: 1, y: {m: 1}}console.log //{x: 1, y: {m: 1}}obj2.y.m = 2; //修改obj2.y.mconsole.log //{x: 1, y: {m: 1}} 原对象未改变console.log //{x: 2, y: {m: 2}}
本文由10bet发布于Web前端,转载请注明出处:javascript深拷贝、浅拷贝和循环引用深入理解_基础
关键词: