1.什么是深拷贝和浅拷贝 ?
在面试时经常会碰到面试官问: 什么是深拷贝和浅拷贝,请举例说明?如何区分深拷贝与浅拷贝,简单来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,如果B没变,那就是深拷贝;
我们先看两个简单的案例
案例1:
案例2:
照常规的思维,o1
应该和a1
是一样的,不会因为另外一个值的改变而发生改变,而这里的o1
却随着o2
的改变而改变了。同样是变量,为什么表示的不一样呢? 为了更好的理解js
的深拷贝和浅拷贝。我们先来理解一些js
基本的概念:目前JavaScript
有五种基本数据类型(也就是简单数据类型),它们分别是:Number
,Boolean
,string
,null
和undefined
。还有一种复杂的数据类型(也叫引用类型),就是对象;常见的引用类型有:object
对象、array
数据、function
函数等......
2.基本数据类型和引用数据类型
ECMAScript
变量可能包含两种不同的数据类型的值:基本类型值和引用类型值。 基本类型指的是哪些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。而引用类型值是指的是哪些保存在堆内存中的对象,意思是变量中保存的实际上就只是一个指针,这个指针指向内存中的另一个位置,而该位置保存对象。
哪什么是堆?什么又是栈?
3.JS中的堆内存和栈内存
计算器语言有一个处理的过程,写的代码会进行解释或编译执行,这个过程是在内存中,内存的使用和 分配,涉及到堆和栈,任何语言都有堆和栈 ,堆和栈都存放在内存中。
3.1 栈内存
栈:JavaScript
的基本数据类型就五种:undefined
、null
、string
、number
和Boolean
。它们都是直接按值存储在栈内存中,每种类型的数据占用的内存空间的大小是确定的。栈由系统自动分配,比如:声明在函数中的一个局部变量var a;
,系统自动在栈中为a开辟空间,只要栈的剩余空间大于所要的申请空间。系统将为程序提供内存,否则将报异常提示栈溢出。
3.2 堆内存
堆:JavaScript
中其他类型的数据被称为引用类型的数据 : 如对象(Object)
、数组(Array)
、函数(Function)
等 …, 它们是通过拷贝和new
出来的,这样的数据存储于堆中;其实,说存储于堆中,也不太准确,因为,引用类型的数据的地址指针是存储于栈中的,当我们想要访问引用类型的值的时候,需要先从栈中获得对象的地址指针,然后,在通过地址指针找到堆中的所需要的数据。
4.引用类型如何实现深拷贝
4.1 Array(数组)
对于数组来讲,我们可以使用slice()
和concat()
来解决上面的问题。
4.1.1 .slice()
4.1.2 .concat()
4.1.3 多维数组也可以吗 ?
我们把arr1改成二维数组再来看看。
var arr1 = ['a', 'b', ['c', 'd']],
arr2 = arr1.concat();
arr2[2][1] = 100;
console.log(arr1); //['a', 'b', ['c', 100]]
console.log(arr2); //['a', 'b', ['c', 100]]
咦, arr2
又改变了arr1
, 看来slice()
和 concat()
只能实现一维数组的深拷贝。
4.2 object(对象)
利用对象的深拷贝实现原理定义一个新的对象,遍历源对象的属性并赋给新对象的属性。
const obj = {
name: '果果',
age: 18
}
const obj2 = new Object();
obj2.name = obj.name;
obj2.age = obj.age
obj.name = '娇娇';
console.log(obj); //Object {name: "'娇娇'", age: 18}
console.log(obj2); //Object {name: "'果果'", age: 18}
4.2.1 JSON.parse(JSON.stringify(obj))
var obj = {
name: '果果',
age: 18
}
var obj2 = JSON.parse(JSON.stringify(obj));
obj.name = '娇娇';
console.log(obj) // {name: "娇娇", age: 18}
console.log(obj2) // {name: "果果", age: 18}
4.2.2 JSON.parse(JSON.stringify(obj))深拷贝的坑
利用JSON.stringify
将js
对象序列化(JSON
字符串),再使用JSON.parse
来反序列化(还原)js
对象;
注意以下几点:
- 如果
obj
里面存在时间对象,JSON.parse(JSON.stringify(obj))
之后,时间对象变成了字符串 - 如果
obj
里有RegExp
、Error
对象,则序列化的结果将只得到空对象。 - 如果
obj
里有函数,undefined
,则序列化的结果会把函数,undefined
丢失。 - 如果
obj
里有NaN
、Infinity
和-Infinity
,则序列化的结果会变成null
。 JSON.stringify()
只能序列化对象的可枚举的自有属性。如果obj
中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))
深拷贝后,会丢弃对象的constructor
。- 如果对象中存在循环引用的情况也无法正确实现深拷贝。