目录
- 浅拷贝和深拷贝都是用来拷贝对象的
- 每创建一个对象,内存地址都会开辟出一块对象空间,对象的内存结构不懂的可以看一下基本数据类型和引用数据类型的区别,对象拷贝就是开辟一块新的对象空间,把对象之前的属性复制过去,拷贝完后两个对象看起来一样,但是内存地址不同,所以它们并不相等。
- 打个比方:去超市买东西,懒得挑,第一种情况,看见别人拿着挑好的东西,你直接去过夺走别人的东西然后去付款,这个就叫对象的变量赋值;第二种情况,你拿了塑料袋,过去夺走别人挑好的,把别人塑料袋里的东西全部倒到自己塑料袋里,再把袋子还给别人,这是浅拷贝;第三种情况,你照着别人的东西一样一样的给你拿了一份,这是深拷贝。
1.对象的变量赋值
let obj = { name:'孙悟空', age:24 } let obj2 = obj console.log(obj);//{name: '孙悟空', age: 24} console.log(obj2);//{name: '孙悟空', age: 24} console.log(obj === obj2);//true // 改变obj2的name obj2.name = '猪八戒' console.log(obj);//{name: '猪八戒', age: 24}
对象的变量赋值,是让新变量指向旧变量所对应的对象的内存地址,所以当新变量的对象属性改变时,旧变量所对应的对象的属性也会跟着变(obj是旧变量,obj2是新变量,obj2修改name属性时,obj也会相应变化)。
2.浅拷贝
浅拷贝就是只拷贝对象的第一层属性,拷贝后的对象有变化时,原对象的第一层属性不会发生变化,但除去第一层的,其他深层的数据依然是公用的,深层级的数据发生变化时也会跟着变。
浅拷贝的实现方法:
- 数组的slice方法
- 拓展运算符 ...
- Object.assign()方法
- 遍历对象属性,把属性添加到一个新对象
let obj = { name:'孙悟空', age:24, child:{ name:'小猴' } } let obj2 = Object.assign({},obj) console.log(obj2); //{name: '孙悟空', age: 24,child: {name: '小猴'}} console.log(obj === obj2); //false obj2.name = '猪八戒' obj2.child.name = '小小猴' console.log(obj2);//{name: '猪八戒', age: 24,child: {name: '小小猴'}} console.log(obj);//{name: '孙悟空', age: 24,child: {name: '小小猴'}} /* 可以看到,obj和obj2不相等,说明已经拷贝成功,当obj2的第一层属性变化时, obj的属性不会发生变化,而当obj2的第二层属性发生变化时,obj的第二层也会跟着变, 只拷贝第一层属性,这就是浅拷贝 */
let arr = ['二狗','狗子','狗剩',{name:'狗蛋'}] let arr2 = arr.slice() console.log(arr2); //['二狗', '狗子', '狗剩',{name:'狗蛋'}] console.log(arr === arr2);//false arr2[0]='大狗' arr2[3].name = '狗娃' console.log(arr2);//['大狗', '狗子', '狗剩',{name:'狗娃'}] console.log(arr);//['二狗', '狗子', '狗剩',{name:'狗娃'}] // 数组是特殊的对象,浅拷贝只拷贝一层,修改后被拷贝的对象不受影响,深层公用,修改会跟着改
let obj = { name: '狗娃', age: 32, child:{ name: '狗剩' } } let obj2 = {...obj} console.log(obj2);//{name: '狗娃', age: 32, child: {name:'狗剩'}} console.log(obj === obj2);//false obj2.age = 1 obj2.child.name = '狗子' console.log(obj2);//{name: '狗娃', age: 1, child: {name:'狗子'}} console.log(obj);//{name: '狗娃', age: 32, child: {name:'狗子'}} // 数组也可以用拓展运算符进行浅拷贝
let obj = { name: '狗娃', age: 32, child:{ name: '狗剩' } } let obj2 = {} for(let item in obj){ obj2[item] = obj[item] } console.log(obj2);//{name: '狗娃', age: 32, child: {name:'狗剩'}} console.log(obj === obj2);//false obj2.age = 1 obj2.child.name = '狗子' console.log(obj2);//{name: '狗娃', age: 1, child: {name:'狗子'}} console.log(obj);//{name: '狗娃', age: 32, child: {name:'狗子'}}
3.深拷贝
对对象进行完全拷贝,不管层级多深。深拷贝后的对象做任何修改都不会对原对象造成影响。
实现深拷贝的方法:
- JSON转换:JSON.parse(JSON.stringify( ))
- 递归遍历对象属性添加到新对象
- structuredClone()方法
let obj = { name: '狗娃', age: 32, child:{ name: '狗剩' } } let obj2 = JSON.parse(JSON.stringify(obj)) console.log(obj2);//{name: '狗娃', age: 32, child: {name:'狗剩'}} console.log(obj === obj2);//false obj2.age = 1 obj2.child.name = '狗子' console.log(obj2);//{name: '狗娃', age: 1, child: {name:'狗子'}} console.log(obj);//{name: '狗娃', age: 32, child: {name:'狗剩'}} // 深拷贝会全部拷贝,一个对象修改,另一个完全不受影响
let obj = { name: '狗娃', age: 32, child:{ name: '狗剩' } } let obj2 = structuredClone(obj) console.log(obj2);//{name: '狗娃', age: 32, child: {name:'狗剩'}} console.log(obj === obj2);//false obj2.age = 1 obj2.child.name = '狗子' console.log(obj2);//{name: '狗娃', age: 1, child: {name:'狗子'}} console.log(obj);//{name: '狗娃', age: 32, child: {name:'狗剩'}}
let obj = { name: '狗娃', age: 32, child:{ name: '狗剩' } } function deepCopy(obj){ // 判断传入的是不是数组,是就创建新数组,不是就创建新对象 let newObj = Array.isArray(obj)?[]:{} // 当obj有值,且typeof为object类型(数组,对象,null),且不为null时进入循环 if(obj && typeof obj === "object" && obj !== null){ for(let item in obj){ // 循环出属性,继续判断属性值是否为对象,数组,如果是就递归,不是就赋值 if(typeof obj[item] === "object" && obj[item] !== null){ // 给新对象添加属性,newObj[item]写法是因为item是动态的属性名 // 通过递归,把所有属性都拷贝到新对象中 newObj[item] = deepCopy(obj[item]) }else{ newObj[item] = obj[item] } } return newObj } } let obj2 = deepCopy(obj) console.log(obj2);//{name: '狗娃', age: 32, child: {name:'狗剩'}} console.log(obj === obj2);//false obj2.age = 1 obj2.child.name = '狗子' console.log(obj2);//{name: '狗娃', age: 1, child: {name:'狗子'}} console.log(obj);//{name: '狗娃', age: 32, child: {name:'狗剩'}}
4.递归
- 递归就是函数内部自己调自己
- 递归也是函数调用栈(栈:先入后出),一层一层往里调用,最后从里往外依次执行
- 递归是循环,得设置终止条件,否则会形成死循环
- 递归每次调用函数都会创建新的函数作用域,所以性能并不好
- 递归一般用于寻找树的某个节点,深拷贝等等
// 求n的阶乘为例 /* n! = n x (n-1)! 4! = 4 x 3! 3! = 3 x 2! 2! = 2 x 1! 1! = 1 */ function jiecheng(n){ if(n===0 || n===1){ return 1 }else{ console.log(n);//10 9 8 7 6 5 4 3 2 return n*jiecheng(n-1) // 10*jiecheng(9)-->9*jiecheng(8)-->...-->2*jiecheng(1) // 求出jiecheng(1),反向执行回去就可以得到jiecheng(10) } } console.log(jiecheng(10));//3628800