浅拷贝与深拷贝
我们在实际开发中经常会对一些数据进行操作,但是我们拿来的数据只有一份,如果我们在源数据上进行操作就会造成数据的损失,所以我们就把源数据拷贝一份,然后用这些拷贝的数据做一些测试或者验证之类的操作,这样也就不会对源数据造成损失。我们实际开发中的数据不是单纯的简单数据类型,更多的是复杂数据类型,大多数都是数组中嵌套数组或者数组中嵌套对象,对象中再嵌套数组等等。所以对于复杂数据类型就不能用赋值的方式进行拷贝,因为复杂数据类型如果按照赋值的方式进行拷贝的话,只是把这个复杂数据类型的地址值赋值给另外一个变量,如果对这个变量进行操作的话,依然会改变源数据。所以针对复杂数据类型的拷贝不能用赋值进行操作。
特别注意:拷贝不能直接赋值,对象赋值的是地址
<script>
var obj = {
name: "张三",
age: 18
}
var newObj = obj; //只是把存储这个对象的地址值给了newObj
newObj.name = "李四";
console.log(obj, newObj); //此时obj这个对象中的name属性的属性值也变为李四
</script>
浅拷贝
浅拷贝:浅拷贝就是只拷贝复杂数据类型最外面的一层,也就是说数据中没有嵌套复杂数据类型。下面的代码会展示浅拷贝
<script>
var obj = {
name: "jerry",
age: 18,
sex: "男"
};
//将obj中的数据拷贝到newObj中
var newObj = {}; //定义一个空对象用来接收拷贝过来的数据
//for in 循环遍历对象,将obj中的每一个属性及属性值赋值给newObj 对象里面的数据是简单数据类型
for (key in obj) {
newObj[key] = obj[key];
}
console.log(obj, newObj); //此时打印的结果就是 obj和newObj一样,里面的数据也是一样的
newObj.name = "tom";
console.log(obj, newObj); //此时再打印的结果就是 obj还是原来的数据,但是newObj中的name属性值已经变为tom
</script>
在ES6中有一个方法直接可以用来进行浅拷贝,可以简化代码
<script>
var obj = {
name: "jerry",
age: 18,
sex: "男"
};
var newObj = {};
Object.assign(newObj, obj); //ES6的方法
console.log(newObj, obj);
newObj.name = "tom";
console.log(obj, newObj);
</script>
深拷贝
在学习深拷贝之前,我们先来了解一下递归,因为深拷贝中我们会用到递归。
递归
递归:如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。简单理解:函数内部自己调用自己, 这个函数就是递归函数
递归:函数调用函数其本身
**注意:**递归函数的作用和循环效果一样,由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件return。
递归小练习
<script>
// 利用递归求1~n的任意一个数的阶乘
//利用递归函数求1~n的阶乘 1 * 2 * 3 * 4 * ..n
function fn(n) {
if (n == 1) { //结束条件:当n为1的时候,不在执行函数 返回即可
return 1;
}
//return就是返回一个值,这个值就是后面这个表达式求出来的结果
// 后面这个表达式的运算过程:
// 第一次执行:4*f(3)
// 第二次执行:4*3*f(2)
// 第三次执行:4*3*2*f(1)
// 第四次执行:4*3*2*1 n为1时函数不再调用
// 就是每次都在调用函数本身,直到n为1时 结束
return n * fn(n - 1);
}
console.log(fn(4));
</script>
<script>
// 利用递归求斐波那契数列
// 利用递归函数求斐波那契数列(兔子序列) 1、1、2、3、5、8、13、21...
// 用户输入一个数字 n 就可以求出 这个数字对应的兔子序列值
// 我们只需要知道用户输入的n 的前面两项(n-1 n-2)就可以计算出n 对应的序列值
function fb(n) {
if (n === 1 || n === 2) { //判断条件 第一个月和第二个月都为1 只要其中一个成立即可
return 1;
}
//return就是返回一个值,这个值就是后面这个表达式求出来的结果
// 后面这个表达式的运算过程:
// 第一次执行:f(6-1) + f(6-2) 即f(5) + f(4)
// 第二次执行:f(4) + f(3) + f(3) + f(2)
// 第三次执行:f(3) + f(2) + f(2) + f(1) + f(2) + f(1) +1
// 第四次执行:f(2) + f(1) + 1 + 1 + 1 + 1 + 1 +1
// 第五次执行:1 + 1 + 1 + 1 + 1 + 1 + 1 +1 n为1时函数不再调用 所以结果为8
// 就是每次都在调用函数本身,直到n为1时 结束
return fb(n - 1) + fb(n - 2);
}
console.log(fb(6));
</script>
<script>
// 利用递归遍历数据
var data = [{
id: 1,
name: '家电',
goods: [{
id: 11,
gname: '冰箱',
goods: [{
id: 111,
gname: '海尔'
}, {
id: 112,
gname: '美的'
},]
}, {
id: 12,
gname: '洗衣机'
}]
}, {
id: 2,
name: '服饰'
}];
function getGoods(arr, id) {
var obj;
arr.forEach(function(item, index) {
// 判断数组中的每个对象的goods属性是否为对象类型(数组也属于对象)
// 然后让函数自调用再遍历里面的数据
if (item.goods instanceof Object) {
obj = getGoods(item.goods, id)
}
// 如果没有goods属性或者该属性不是对象类型 直接判断查找
else if (item.id == id) {
obj = item;
}
});
return obj;
}
var obj = getGoods(data, 111)
console.log(obj);
</script>
通过上面的案例,递归的作用应该很清晰了,接下来我们介绍深拷贝。
深拷贝
深拷贝:就是数据内部还嵌套复杂数据类型,嵌套的复杂数据类型不能按照浅拷贝那样拷贝,否则又会出现源数据被改变,所以嵌套的数据依然得遍历拷贝,所以就得用到递归,就是遇到嵌套的复杂的数据类型的时候再次调用函数本身去遍历拷贝。
<script>
var obj = {
name: '张三',
age: 22,
sex: '男',
likeColor: ['red', 'blue', 'yellow'],
message: {
score: 99,
play: "打篮球"
}
};
// 定义新的对象用于接收拷贝的数据
var newObj = {};
//封装方法 用于递归
function getObj(newObj, obj) {
// 遍历对象
for (key in obj) {
// 判断数据嵌套里有没有数组,如果有数组再调用该函数
if (obj[key] instanceof Array) {
newObj[key] = []; //用数组接收
getObj(newObj[key], obj[key]);
} else if (obj[key] instanceof Object) { //判断数据嵌套里有没有对象,如果有对象再调用该函数
newObj[key] = {}; //用对象接收
getObj(newObj[key], obj[key]);
} else {
newObj[key] = obj[key];
}
}
}
getObj(newObj, obj);
newObj.message.play = '踢足球';
console.log(obj);
console.log(newObj);
</script>