JavaScript----深拷贝、浅拷贝
为说清楚二者使用过程中的区别,首先介绍一些JavaScript的基本知识
JavaScript变量包含两种不同数据类型:基本类型和引用类型
基本类型,有以下6种:
number、string、boolean、null、undefined、symbol
引用类型,指那些可能由多个值构成的对象,只有一个:
object(Array、Function、RegExp、Math、Date)
将一个值赋给变量时,解析器必须确定这个值是基本类型还是引用类型。
基本类型按值访问,课题操作保存在变量中实际的值。
引用类型的值是保存在内存中的对象。JavaScript不允许直接访问内存中的位置,即不能直接操作对象的内存空间,在操作对象时,实际上是在操作对象的引用而不是实际的对象
JavaScript变量的存储方式--栈(stack)和堆(heap)
- 栈:自动分配内存空间,系统自动释放,存放的是 基本数据类型的值 和 引用类型的地址
- 堆:动态分配的内存,大小不定,不会自动释放。存放的是引用类型的值。
JavaScript的值传递与址传递
基本类型和引用类型最大的区别就是 传值与传址的区别
- 值传递:基本类型采用的是值传递。
let a = 100; // 定义变量a并赋值100
let b = a; // 将a的值赋给b(a,b都是基本类型:值传递)
b++; //b=101
console.log(a);// 100
console.log(b);// 101
- 址传递:引用类型使用地址传递,将存放在栈内存中的地址赋值给接收的变量。
let a = [1, 2, 3];// Array属于引用类型
let b = a; // 地址传递:将a的地址赋值给b,a和b指向同一块地址
b.push(4); // b数组的末尾追加一个4
console.log(a); // [1, 2, 3, 4]
console.log(b); // [1, 2, 3, 4]
分析
a和b都是引用类型,采用址传递,即a的地址传递给b,那么a和b指向同一个地址(引用类型的地址存放在栈内存中),而这个地址都指向堆内存中引用类型的值。当b改变了这个值的同时,由于a也指向了这个值,所以a的值也跟着改变。
深拷贝和浅拷贝
- 深拷贝
深拷贝不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深复制的方法递归复制到新对象上,所以对一个对象的修改并不会影响另一个对象。
- 浅拷贝
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。
以数组和对象的深浅拷贝为例
浅拷贝的解决就是先设置一个新的对象obj2,通过遍历的方式将obj1对象的值一一赋值给obj2对象。
代码实现:
// 数组的浅拷贝的解决
let a = [1, 2, 3];
let b = [];
for (let i in a) {
b[i] = a[i];
}
b.push(4);
console.log(a); // [1, 2, 3]
console.log(b); // [1, 2, 3, 4]
// 对象的浅拷贝的解决
let obj1 = {
'a' : 1,
'b' : 2,
'c' : 3
};
let obj2 = {};
for (let i in obj1) {
obj2[i] = obj1[i];
}
obj2['d'] = 4;
console.log(obj1); // [a:1, b:2, c:3]
console.log(obj2); // [a:1, b:2, c:3, d:4]
但上面代码只能实现一层的拷贝,无法进行深层次的拷贝,封装函数再次通过对象数组嵌套测试,代码如下:
function shallowCopy(obj1, obj2) {
for (let key in obj1) {
if (obj1.hasOwnProperty(key)){
obj2[key] = obj1[key]
}
}
}
let obj1 = {
fruits: ['apple', 'bannar'],
num: 100
}
let obj2 = {}
shallowCopy(obj1, obj2)
obj2.fruits[0] = 'orange'
console.log(obj1.fruits[0]); // orange
console.log(obj2.fruits[0]); // orange
结果证明,无法进行深层次的拷贝,这个时候我们可以使用深拷贝来完成。
深拷贝:实现真正意义上的数组和对象的拷贝,可以通过递归调用浅拷贝的方式实现。
代码封装实现如下:
function deepCopy(obj) {
// 定义一个对象,用来确定当前的参数是数组还是对象
let objArray = Array.isArray(obj) ? [] : {};
// 如果obj存在,且类型为objecct
if (obj && typeof obj === "object") {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
// 如果obj的子元素是对象,递归操作
if (obj[key] && typeof obj[key] === "object") {
objArray[key] = deepCopy(obj[key]);
} else {
// 如果不是,直接赋值
objArray[key] = obj[key];
}
}
}
}
return objArray; // 返回新对象
}
let obj1 = {
fruits = ['apple', 'bannar'],
num: 100
}
let obj2 = deepCopy(obj1);
obj2.fruits[0] = 'orange';
console.log(obj1.fruits[0]); // apple
console.log(obj2.fruits[0]); // orange
结果证明,deepCopy函数可以实现深层次的克隆。
jQuery中提供了extend工具方法:
jQuery.extend([deep], target, object1, [objectN]);
参数介绍:
deep: boolean类型,true表示深拷贝
数组深拷贝的方法
- for循环
let arr1 = [1,2,3];
let arr2 = copyArr(arr1);
function copyArr(arr) {
let res = [];
for (let i = 0, length = arr.length; i < length; i++){
res.push(arr[i]);
}
return res;
}
- slice
let arr1 = [1,2,3];
let arr2 = arr1.slice(0);
- concat
let arr1 = [1,2,3];
let arr2 = arr1.concat();
- 扩展运算符(...)
let arr1 = [1,2,3];
let [...arr2] = arr1;
- Array.from
let arr1 = [1,2,3];
let arr2 = Array.from(arr1);
对象深拷贝的方法
- for循环
let obj1 = {count : 1, name : 'grace' , age : 1};
let obj2 = copyObj(obj) {
let res = {};
for (let key in obj) {
res[key] = obj[key];
}
return res;
}
- 利用JSON
let obj1 = {count : 1, name : 'grace' , age:1};
let obj2 = JSON.parse(JSON.stringify(obj1));
- 扩展运算符
let obj1 = {count : 1, name: 'grace' , age : 1};
let {...obj2} = obj1;
实现数组和对象的深拷贝,合成版
function deepCopy(obj) {
let result = Array.isArray(obj) ? [] : {};
if (obj && typeof obj === 'object') {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === 'object') {
result[key] = deepCopy(obj[key]);
} else {
result[key] = obj[key];
}
}
}
}
return result;
}