深拷贝和浅拷贝的起源
- Js变量包含两种不同数据类型的值:基本类型和引用类型
- 基本类型指的是简单的数据段,包括ES6中新增的一共是6种:number、string、boolean、null、undefined、symbol
- 引用类型值指的是那些可能由多个值构成的对象,只有一种:object
在将一个值赋值给变量时,解析器必须确定这个值是基本类型值还是引用类型值。基本类型是按值访问的,因为可以操作保存在变量中的实际的值。
引用类型的值是保存在内存中的对象。与其他语言不同,JS不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。
- Js变量的存储方式----栈(stack)和堆(heap)
- 栈:自动分配内存空间,系统自动释放,里面存放的是基本类型的值和引用类型的地址
- 堆:动态分配的内存,大小不定,也不会自动释放。里面存放引用类型的值。
- Js值传递与引用传递
基本类型与引用类型最大的区别就是传值与传地址的区别
- 值传递:基本类型采用的是值传递
- 地址传递:引用类型则是地址传递,将存放在栈内存中的地址赋值给接收的变量
问题引出
通过上面的描述,我们了解到对象类型在赋值的过程中其实是复制了地址,从而会导致在改变了一方其他也会被改变的情况。通常在开发过程中我们不希望出现这样的问题
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
let a = { age: 1 } let b = a a.age = 2 console.log(b.age) // 2
浅拷贝
浅拷贝解决思路就是先设置一个新的对象obj2,通过遍历的方式将obj1对象的值一一赋值给新对象
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
let obj1 = { us: 'jett', address: { city: 'hz' }, age: 22 } let obj2 = {} for(let key in obj1) { obj2[key] = obj1[key] } console.log(obj2) // {us: "jett", address: {city: "hz"}, age: 22} obj2.address.city = 'jx' console.log(obj1) // {us: "jett", address: {city: "jx"}, age: 22} // 此时obj1的address内的city也变了
通过上面的代码可以看出,浅拷贝只能实现一层的改变,如果obj1中的属性为一个对象,此时拷贝的是地址,所以并不是深拷贝。
还有一种我们可以通过Object.assign来实现浅拷贝,Object.assign只会拷贝所有的属性值到新的对象,如果属性值是对象的话,拷贝的是地址。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
let a = { age: 1 } let b = Object.assign({}, a) a.age = 2 console.log(b.age) // 1
另外还可以通过扩展运算符...来实现浅拷贝
通常浅拷贝可以解决大部分的问题,但是当我们想拷贝属性为对象的值时,就要使用深拷贝了。
深拷贝
深拷贝,就是能够实现真正意义上的数组和对象的拷贝,我们可以用过递归调用浅拷贝的方式来实现
递归实现:(很多边缘情况,以及特殊的值暂未考虑)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
let obj1 = { us: 'jett', address: { city: 'hz' }, age: 22 } let obj2 = {} function deepCopy(obj1, obj2) { for(let key in obj1) { if(typeof obj1[key] === 'object'){ console.log('-----') obj2[key] = {} deepCopy(obj1[key], obj2[key]) }else { obj2[key] = obj1[key] } } } deepCopy(obj1, obj2) console.log(obj2) // {us: "jett", address: {city: "hz"}, age: 22} obj2.address.city = 'jx' console.log('obj1', obj1) // {us: "jett", address: {city: "hz"}, age: 22} // 此时obj1的address内的city没变,实现了深拷贝
也可以通过JSON.parse(JSON.stringify(object))来解决
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
let a = { age: 1, jobs: { first: 'FE' } } let b = JSON.parse(JSON.stringify(a)) a.jobs.first = 'native' console.log(b.jobs.first) // FE
但是该方法有局限性
- 会忽略undefined
- 会忽略symbol
- 不能序列化函数
- 不能解决循环引用的对象
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
let a = { age: undefined, sex: Symbol('male'), jobs: function() {}, name: 'yck' } let b = JSON.parse(JSON.stringify(a)) console.log(b) // {name: "yck"}
但是在通常情况下,复杂数据都是可以序列化的,所以这个函数可以解决大部分问题。
自己实现一个深拷贝是很困难的,需要我们考虑好多边界情况,比如原型链如何处理、DOM如何处理等,这里实现的深拷贝只是一个简易版
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
function deepClone(obj) { function isObject(o) { return (typeof o === 'object' || typeof o === 'function') && o !== null } if (!isObject(obj)) { throw new Error('非对象') } let isArray = Array.isArray(obj) let newObj = isArray ? [...obj] : { ...obj } Reflect.ownKeys(newObj).forEach(key => { newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] }) return newObj }
对于JS的浅拷贝和深拷贝方法还有很多,这里只是介绍了常见的几种。