javascript 关于赋值、浅拷贝、深拷贝

以下是对关于赋值、浅拷贝、深拷贝的学习


数据类型

说这个话题之前,先扯一下JS的基本数据类型。

大家都知道JS中的数据类型分为:
基本类型:string, number, boolean, null, undefined,symbol(ES6新增)
引用类型:Object,特殊的有Array, Function, Date, Math, RegExp, Error等((Object类)有常规名值对的无序对象{a:1},数组[1,2,3],以及函数等)

那什么是引用类型呢?这又得扯上JS的内存机制了。

JS的内存跟其他的内存差不多,分为堆(heap)和栈(stack)。

  1. 栈和堆
    栈:由系统自动分配,自动回收,效率高,但容量小。
    堆:由程序员手动分配内存,并且手动销毁(高级语言如JS中有垃圾自动回收机制),效率不如栈,但容量大。
    请注意区分数据结构中所说的堆栈和内存中的堆栈是两回事。
  2. 基本类型和引用类型分配位置(栈,堆)
    JS的基本类型分配在栈中,而因为引用类型大小的不固定,系统将存储该引用类型的地址存在栈中,并赋值给变量本身,而具体的内容存在堆中。所以当访问一个对象的时候,先访问栈中它的地址,然后按照这个地址去堆中找到它的实际内容。

  1. a.基本类型–名值存储在栈内存中,例如let a=1;
    如图
    当你b=a复制时,栈内存会新开辟一个内存,例如这样:
    如图

所以当复制的时候,对于基本类型的变量,系统会为新的变量在栈中开辟一个新的空间,赋予相同的值,然后这两个变量就各自独立,毫无牵连。所以当你此时修改a=2,对b并不会造成影响,因为此时的b已自食其力,翅膀硬了,不受a的影响了.

  1. b.引用数据类型–名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值
    如图
    而对于引用类型的变量,新的变量复制的是那个对象在堆中的地址,这两个变量指向的是同一个对象。

例子:简单的引用拷贝(浅拷贝)

var obj = {name: 'Yecao'};
var obj2 = obj;
console.log(obj2.name); // 'Yecao'
obj2.name = 'Claire';
console.log(obj.name); //'Claire'

以上是个很简单的例子,obj2复制了obj之后,两个其实指向的是同一个对象。

而当我们a[0]=1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。

如图

实现简单的深拷贝
这么我们封装一个深拷贝的函数(PS:只是一个基本实现的展示,并非最佳实践)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}
let a=[1,2,3,4],
b=deepClone(a);
a[0]=2;
console.log(a,b);

可以看到
如图

跟之前想象的一样,现在b脱离了a的控制,不再受a影响了。

这里再次强调,深拷贝,是拷贝对象各个层级的属性
如图

第一层的属性确实深拷贝,拥有了独立的内存,但更深的属性却仍然公用了地址,所以才会造成上面的问题。

同理,concat方法与slice也存在这样的情况,他们都不是真正的深拷贝,这里需要注意。

slice 和 concat

Array 的 slice 和 concat 方法 和 jQuery 中的 extend 复制方法,他们都会复制第一层的值,对于 第一层 的值都是 深拷贝,而到 第二层 的时候 Array 的 slice 和 concat 方法就是 复制引用 ,jQuery 中的 extend 复制方法 则 取决于 你的 第一个参数, 也就是是否进行递归复制。所谓第一层 就是 key 所对应的 value 值是基本数据类型,也就像上面栗子中的name、age,而对于 value 值是引用类型 则为第二层,也就像上面栗子中的 company。

总结

  1. JS的基本类型不存在浅拷贝还是深拷贝的问题,深拷贝与浅拷贝的概念只存在于引用类型。
  2. 对于仅仅是复制了引用(地址),换句话说,复制了之后,原来的变量和新的变量指向同一个东西,彼此之间的操作会互相影响,为 浅拷贝。
    而如果是在堆中重新分配内存,拥有不同的地址,但是值是一样的,复制后的对象与原来的对象是完全隔离,互不影响,为 深拷贝。
  3. 深浅拷贝 的主要区别就是:复制的是引用(地址)还是复制的是实例。

参考链接

博客圆

知乎


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!