栈和堆

stack为自动分配的内存空间,它由系统自动释放;而heap则是动态分配的内存,大小不定也不会自动释放。

基本类型和引用类型

  • 基本类型:存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配。5种基本数据类型有Undefined、Null、Boolean、Number 和 String,它们是直接按值存放的,所以可以直接访问。
  • 引用类型:存放在堆内存中的对象,变量实际保存的是一个指针,这个指针指向另一个位置。每个空间大小不一样,要根据情况开进行特定的分配。当我们需要访问引用类型(如对象,数组,函数等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。

传值与传址

前面之所以要说明什么是内存中的堆、栈以及变量类型,实际上是为下文服务的,就是为了更好的理解什么是“浅拷贝”和“深拷贝”。基本类型与引用类型最大的区别实际就是传值与传址的区别。测试用例:
1
2
3
4
5
6
7
8
9
10
var a = [1,2,3,4,5];
var b = a;
var c = a[0];
alert(b);//1,2,3,4,5
alert(c);//1
//改变数值
b[4] = 6;
c = 7;
alert(a[4]);//6
alert(a[0]);//1
从上面我们可以得知,当我改变b中的数据时,a中数据也发生了变化;但是当我改变c的数据值时,a却没有发生改变。
这就是传值与传址的区别。因为a是数组,属于引用类型,所以它赋予给b的时候传的是栈中的地址(相当于新建了一个不同名“指针”),而不是堆内存中的对象。而c仅仅是从a堆内存中获取的一个数据值,并保存在栈中。所以b修改的时候,会根据地址回到a堆中修改,c则直接在栈中修改,并且不能指向a堆内存中。

浅拷贝

在定义一个对象或数组时,变量存放的往往只是一个地址。当我们使用对象拷贝时,如果属性是对象或数组时,这时候我们传递的也只是一个地址。因此子对象在访问该属性时,会根据地址回溯到父对象指向的堆内存中,即父子对象发生了关联,两者的属性值会指向同一内存空间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = {
key1:"11111"
}
function Copy(p) {
var c = {};
for (var i in p) {
  c[i] = p[i];
}
return c;
  }
a.key2 = ['小辉','小辉'];
var b = Copy(a);
b.key3 = '33333';
alert(b.key1); //1111111
alert(b.key3); //33333
alert(a.key3); //undefined
a对象中key1属性是字符串,key2属性是数组。a拷贝到b,12属性均顺利拷贝。给b对象新增一个字符串类型的属性key3时,b能正常修改,而a中无定义。说明子对象的key3(基本类型)并没有关联到父对象中,所以undefined。
1
2
3
1 b.key2.push("大辉");
2 alert(b.key2); //小辉,小辉,大辉
3 alert(a.key2); //小辉,小辉,大辉
但是,若修改的属性变为对象或数组时,那么父子对象之间就会发生关联。从以上弹出结果可知,我对b对象进行修改,a、b的key2属性值(数组)均发生了改变。其在内存的状态.

原因是key1的值属于基本类型,所以拷贝的时候传递的就是该数据段;但是key2的值是堆内存中的对象,所以key2在拷贝的时候传递的是指向key2对象的地址,无论复制多少个key2,其值始终是指向父对象的key2对象的内存空间。

深拷贝

或许以上并不是我们在实际编码中想要的结果,我们不希望父子对象之间产生关联,那么这时候可以用到深拷贝。既然属性值类型是数组和或象时只会传址,那么我们就用递归来解决这个问题,把父对象中所有属于对象的属性类型都遍历赋给子对象即可。测试代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Copy(p, c) {
var c = c || {}; //如果c存在则赋值,否则初始化为{}
for (var i in p) { //遍历p
  if (typeof p[i] === 'object') { //如果 是 对象
     c[i] = (p[i].constructor === Array) ? [] : {}; //constructor方法返回一个对象的属性,如果是array则复制[] 否则 复制 {}
     Copy(p[i], c[i]); //递归 把p[i]放进c[i]的 []或{}中。
  } else {
     c[i] = p[i]; // 直接赋值
  }
}
return c;
  }
a.key2 = ['小辉','小辉'];
var b={};
b = Copy(a,b);
b.key2.push("大辉");
alert(b.key2); //小辉,小辉,大辉
alert(a.key2); //小辉,小辉

由上可知,修改b的key2数组时,没有使a父对象中的key2数组新增一个值,即子对象没有影响到父对象a中的key2。其存储模式大致如下:

深拷贝的几中方法:

  • 方法一:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    function deepCopy(obj){
    var o;
    switch(typeof obj){
    case 'undefined': break;
    case 'string' : o = obj + '';break;
    case 'number' : o = obj - 0;break;
    case 'boolean' : o = obj;break;
    case 'object' :
    if(obj === null){
    o = null;
    }else{
    if(obj instanceof Array){
    o = [];
    for(var i = 0, len = obj.length; i < len; i++){
    o.push(deepCopy(obj[i]));
    }
    }else{
    o = {};
    for(var k in obj){
    o[k] = deepCopy(obj[k]);
    }
    }
    }
    break;
    default:
    o = obj;break;
    }
    return o;
    }
  • 方法二:

    1
    2
    3
    function deepCopy(obj) {
    return JSON.parse(JSON.stringify(obj));
    }
  • 方法三:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function deepCopy(obj){
    var newobj, obj;
    if (obj.constructor == Object){
    newobj = new obj.constructor();
    }else{
    newobj = new obj.constructor(obj.valueOf());//valueOf()方法返回 Array 对象的原始值
    }
    for(var key in obj){
    if ( newobj[key] != obj[key] ){
    if ( typeof(obj[key]) == 'object' ){
    newobj[key] = deepCopy(obj[key]);
    }else{
    newobj[key] = obj[key];
    }
    }
    }
    newobj.toString = obj.toString;
    newobj.valueOf = obj.valueOf;
    return newobj;
    }
  • 方法四:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var cloneObj = function(obj){
    var str, newobj = obj.constructor === Array ? [] : {};
    if(typeof obj !== 'object'){
    return;
    } else if(window.JSON){
    str = JSON.stringify(obj), //系列化对象
    newobj = JSON.parse(str); //还原
    } else {
    for(var i in obj){
    newobj[i] = typeof obj[i] === 'object' ?
    cloneObj(obj[i]) : obj[i];
    }
    }
    return newobj;
    };
  • 方法五:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function deepCopy(p,c){
    c = c || {};
    for (var i in p){
    if(p.hasOwnProperty(i)){
    if(typeof p[i] === 'object'){
    c[i] = Array.isArray(p[i]) ? [] : {};
    deepCopy(p[i],c[i]);
    }else{
    c[i] = p[i];
    }
    }
    }
    return c;
    }

    转自:http://www.cnblogs.com/chengguanhui/p/4737413.html

    https://blog.csdn.net/flyingpig2016/article/details/52895620