JavaScript中的栈和堆内存,作用域,以及$.extend()的深度复制和浅复制(一)
语法:jQuery.extend( [deep ], target, object1 [, objectN ] );
深浅拷贝对应的参数就是[deep],是可选的,为true或false。默认情况是false(浅拷贝),并且false是不能够显示的写出来的。如果想写,只能写true(深拷贝)
前面之所以要说明什么是内存中的堆、栈以及变量类型,实际上是为下文服务的,就是为了更好的理解什么是“浅拷贝”和“深拷贝”。
基本类型与引用类型最大的区别实际就是传值与传址的区别。测试用例:
var a = [1,2,3,4,5]; var b = a; //a赋予给b的时候传的是栈中的地址(相当于新建了一个不同名“指针”),b这个指针也是指向a指向的堆内存中的对象 var c = a[0]; //而c仅仅是从a堆内存中获取的一个数据值,并保存在栈中。 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堆内存中。注意看下面的箭头的指向。
注意区别这两个:
var obj = {a:"123"};var obj1 = obj;console.log(obj===obj1);//输出的是true var obj = {a:"123"};var obj1 = $.extend(true,obj);console.log(obj===obj1);//输出的是false
var obj = {a:"123"};var obj1 = $.extend({},obj);console.log(obj===obj1);//输出的是false
5、浅拷贝
前面已经提到,在定义一个对象或数组时,变量存放的往往只是一个地址。当我们使用对象拷贝时,如果我们定义的对象的属性是对象或数组时,这时候我们传递的也只是一个地址。因此子对象在访问该属性时,会根据地址回溯到父对象指向的堆内存中,即父子对象发生了关联,两者的属性值会指向同一内存空间。
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,key1,key2属性均顺利拷贝。给b对象新增一个字符串类型的属性key3时,b能正常修改,而a中无定义。说明子对象的key3(基本类型)并没有关联到父对象中,所以undefined。
b.key2.push("大辉"); alert(b.key2); //小辉,小辉,大辉 alert(a.key2); //小辉,小辉,大辉
但是,若修改的属性变为对象或数组时,那么父子对象之间就会发生关联。从以上弹出结果可知,我对b对象进行修改,a、b的key2属性值(数组)均发生了改变。其在内存的状态,可以用下图来表示。
原因是key1的值属于基本类型,所以拷贝的时候传递的就是该数据段;但是key2的值是堆内存中的对象,所以key2在拷贝的时候传递的是指向key2对象的地址,无论复制多少个key2,其值始终是指向父对象的key2对象的内存空间。
浅拷贝的:
test1:
//1.对象的复制
var object1 = {c:30};
var object2 = {
b:{
mm:333
},
c:100
};
console.log('原来的object1--->'+JSON.stringify(object1));
console.log('原来的object2--->'+JSON.stringify(object2));
var object = $.extend(object1, object2);
console.log('新的object1--->'+JSON.stringify(object1));
console.log('新的object2--->'+JSON.stringify(object2));
console.log('返回的object--->'+JSON.stringify(object));
//打印出来的结果
原来的object1--->{"c":30}
原来的object2--->{"b":{"mm":333},"c":100}
新的object1--->{"c":100,"b":{"mm":333}}
新的object2--->{"b":{"mm":333},"c":100}
返回的object--->{"c":100,"b":{"mm":333}}
//2.数组的浅复制一
var arr1 = [1, 2, [3, 4], {a: 5, b: 6}, 7],
arr2 = $.extend([], arr1);
console.log('原来的arr1--->'+JSON.stringify(arr1));
console.log('原来的arr2--->'+JSON.stringify(arr2));
arr2[1] = 10;
arr2[2][0]=77;
console.log('新的arr1--->'+JSON.stringify(arr1));
console.log('新的arr2--->'+JSON.stringify(arr2));
//打印出来的结果
原来的arr1--->[1,2,[3,4],{"a":5,"b":6},7]
原来的arr2--->[1,2,[3,4],{"a":5,"b":6},7]
新的arr1--->[1,2,[77,4],{"a":5,"b":6},7]
新的arr2--->[1,10,[77,4],{"a":5,"b":6},7]
数组的浅复制二
arr2 = $.extend([],[1,2,3],[3,4,5]);
console.log('arr2--->'+JSON.stringify(arr2));
打印的结果:
arr2--->[3,4,5]
text2:
var object1 = {c:30};
var object2 = {
b:{
mm:333
},
c:100
};
console.log('原来的object1--->'+JSON.stringify(object1));
console.log('原来的object2--->'+JSON.stringify(object2));
var object = $.extend(object1, object2);
object1.b.mm = 600;
console.log('新的object1--->'+JSON.stringify(object1));
console.log('新的object2--->'+JSON.stringify(object2));
console.log('返回的object--->'+JSON.stringify(object));
原来的object1--->{"c":30}
原来的object2--->{"b":{"mm":333},"c":100}
新的object1--->{"c":100,"b":{"mm":600}}
新的object2--->{"b":{"mm":600},"c":100}
返回的object--->{"c":100,"b":{"mm":600}}
由测试结果知道,jQuery中$.extend(object1, object2);可以浅拷贝对象,拷贝之后,改变其中一个对象的属性值,对另外一个也会受到影响。
6、深拷贝
或许以上并不是我们在实际编码中想要的结果,我们不希望父子对象之间产生关联,那么这时候可以用到深拷贝。既然属性值类型是数组和或象时只会传址,那么我们就用递归来解决这个问题,把父对象中所有属于对象的属性类型都遍历赋给子对象即可。测试代码如下:
function Copy(p, c) { var c = c || {}; for (var i in p) { if (typeof p[i] === 'object') { c[i] = (p[i].constructor === Array) ? [] : {}; Copy(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); //小辉,小辉
深度拷贝test1:
var object1 = {c:30}; var object2 = { b:{ mm:333 }, c:100 }; console.log('原来的object1--->'+JSON.stringify(object1)); console.log('原来的object2--->'+JSON.stringify(object2)); var object = $.extend(true,object1, object2); object1.b.mm = 600; console.log('新的object1--->'+JSON.stringify(object1)); console.log('新的object2--->'+JSON.stringify(object2)); console.log('返回的object--->'+JSON.stringify(object));
原来的object1--->{"c":30}
原来的object2--->{"b":{"mm":333},"c":100}
新的object1--->{"c":100,"b":{"mm":600}}
新的object2--->{"b":{"mm":333},"c":100}
返回的object--->{"c":100,"b":{"mm":600}}
由测试结果知道,jQuery中$.extend(true,object1, object2);可以深拷贝对象,拷贝之后,改变其中一个对象的属性值,对另外一个没有影响。
2.数组的深拷贝一
var arr1 = [1, 2, [3, 4], {a: 5, b: 6}, 7],
arr2 = $.extend(true,[], arr1);
console.log('原来的arr1--->'+JSON.stringify(arr1));
console.log('原来的arr2--->'+JSON.stringify(arr2));
arr2[1] = 10;
arr2[2][0]=77;
console.log('新的arr1--->'+JSON.stringify(arr1));
console.log('新的arr2--->'+JSON.stringify(arr2));
//打印的结果
原来的arr1--->[1,2,[3,4],{"a":5,"b":6},7]
原来的arr2--->[1,2,[3,4],{"a":5,"b":6},7]
新的arr1--->[1,2,[3,4],{"a":5,"b":6},7]
新的arr2--->[1,10,[77,4],{"a":5,"b":6},7]
数组的深复值二
arr2 = $.extend(true,[], [1,2,3],[3,4,5]);
console.log('arr2--->'+JSON.stringify(arr2))
打印的结果:
arr2--->[3,4,5]
由上可知,修改b的key2数组时,没有使a父对象中的key2数组新增一个值,即子对象没有影响到父对象a中的key2。其存储模式大致如下: