javascript的变量类型
基本类型:存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配。
5种基本数据类型有Undefined、Null、Boolean、Number 和 String,它们是直接按值存放的,所以可以直接访问。引用类型:存放在堆内存中的对象,变量实际保存的是一个内存地址,这个内存地址指向另一个内存空间。每个空间大小不一样,要根据情况开进行特定的分配。
当我们需要访问引用类型(如对象,数组,函数等)的值时,首先从栈中获得该对象的内存地址,然后再从堆内存中取得所需的数据。
javascript存储对象都是存地址的,所以浅拷贝会导致 arr1 和arr2 指向同一块内存地址。改变了其中一方的内容,都是在原来的内存上做修改会导致拷贝对象和源对象都发生改变,而深拷贝是开辟一块新的内存地址,将原对象的各个属性逐个复制进去。对拷贝对象和源对象各自的操作互不影响。
例如:数组拷贝
1 | //浅拷贝,双向改变,指向同一片内存空间 |
浅拷贝
简单的引用复制
1 | var a = { |
a对象中key1属性是字符串,key2属性是数组。a拷贝到b,key1、key2属性均顺利拷贝。给b对象的key2属性进行修改时,a对象的key2属性也会随之改变。说明a,b对象上的key2属性指向的是同一个内存地址(x1),当对b对象的key2属性进行修改时,实际上是修改的x1指向的内存空间。
1 | b.key1 = 1; |
给b对象的key1属性进行修改时,a对象的key1属性不会改变。这是由于a,b对象的key1属性都是基本数据类型,会直接存储在内存中,b对象的key1的修改不会对a对象的key1产生影响。其在内存的状态,可以用下图来表示。
深拷贝
递归遍历
1 | function deepCopy(p, c) { |
由上可知,修改b的key2数组时,没有使a父对象中的key2数组新增一个值,即子对象没有影响到父对象a中的key2。其存储模式大致如下:
Array的slice和concat方法
Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。之所以把它放在深拷贝里,是因为它看起来像是深拷贝。而实际上它是浅拷贝。原数组的元素会按照下述规则拷贝:
- 如果该元素是个对象引用 (不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。
- 对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。
例子如下:
1 | var array = [1,2,3]; |
可以看出,concat和slice返回的不同的数组实例,这与直接的引用复制是不同的。而从另一个例子可以看出Array的concat和slice并不是真正的深复制,数组中的对象元素(Object,Array等)只是复制了引用。如下:
1 | var array = [1, [1,2,3], {name:"array"}]; |
JSON对象的parse和stringify
JSON对象是ES5中引入的新的类型(支持的浏览器为IE8+),JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringify方法可以将JS对象序列化成JSON字符串,借助这两个方法,也可以实现对象的深拷贝。
1 | //例1 |
这种方法使用较为简单,可以满足基本的深拷贝需求,而且能够处理JSON格式能表示的所有数据类型,但是对于正则表达式类型、函数类型等无法进行深拷贝(而且会直接丢失相应的值)。