基本类型
基本数据类型有7
种:Number
、String
、Boolean
、Null
、Undefined
、Symbol(ES6)
、BigInt(ES10)
。变量均按值存放于栈中,赋值直接用=
即可。
引用类型
引用数据类型有1
种:Object
。变量内存地址存放于栈中,值存在堆中,引用类型的赋值与下面讨论的浅拷贝
与深拷贝
密切相关。
浅拷贝
首先声明 浅拷贝 ≠ 赋值
。
赋值=
赋的是对象的内存地址,两个对象指向堆中同一份存储空间,互相影响。
letobj1={name:'瑾行',list:['JS','CSS','HTML']}letobj2=obj1obj2.name='七金'obj2.list[0]='Java'//{name:'七金',list:['Java','CSS','HTML']}//{name:'七金',list:['Java','CSS','HTML']}
浅拷贝是在堆中创建新的内存空间,拷贝后对象的基本数据类型互不影响,但引用类型依然共享同一份存储空间,会互相影响。
functionshallowClone(obj1){letobj2={}for(letiinobj1){obj2[i]=obj1[i]}returnobj2;}letobj1={name:'瑾行',list:['JS','CSS','HTML']}letobj2=shallowClone(obj1)obj2.name='七金'obj2.list[0]='Java'console.log(obj1,obj2)//{name:'瑾行',list:['Java','CSS','HTML']}//{name:'七金',list:['Java','CSS','HTML']}
Array
整理可数组浅拷贝相关api
。
扩展运算符
letarr1=[1,[2],3]letarr2=[...arr1]arr2[0]=4arr2[1].push(5)console.log(arr1,arr2)//[1,[2,5],3][4,[2,5],3]
Array.prototype.slice
letarr1=[1,[2],3]letarr2=arr1.slice()arr2[0]=4arr2[1].push(5)console.log(arr1,arr2)//[1,[2,5],3][4,[2,5],3]
Array.prototype.concat
letarr1=[1,[2],3]letarr2=arr1.concat([])arr2[0]=4arr2[1].push(5)console.log(arr1,arr2)//[1,[2,5],3][4,[2,5],3]
Array.from
将类数组或可迭代对象创建一个新的浅拷贝数组实例。
letarr1=[1,[2],3]letarr2=Array.from(arr1)arr2[0]=4arr2[1].push(5)console.log(arr1,arr2)//[1,[2,5],3][4,[2,5],3]
Array.prototype.map
letarr1=[1,[2],3]letarr2=arr1.map(item=>item)arr2[0]=4arr2[1].push(5)console.log(arr1,arr2)//[1,[2,5],3][4,[2,5],3]
Array.prototype.filter
letarr1=[1,[2],3]letarr2=arr1.filter(item=>item)arr2[0]=4arr2[1].push(5)console.log(arr1,arr2)//[1,[2,5],3][4,[2,5],3]
Array.prototype.reduce
reduce
这里可能有点滥竽充数?,没有真正体现它的价值,算是提供一种新奇的思路吧。
letarr=[1,[2],3]letarr2=arr.reduce((arr1,item)=>{arr1.push(item)returnarr1},[])arr2[0]=4arr2[1].push(5)console.log(arr,arr2)//[1,[2,5],3][4,[2,5],3]
Object
整理可对象浅拷贝相关api
。
扩展运算符
letobj1={name:'瑾行',list:['JS','CSS','HTML']}letobj2={...obj1}obj2.name='七金'obj2.list[0]='Java'console.log(obj1,obj2)//{name:'瑾行',list:['Java','CSS','HTML']}//{name:'七金',list:['Java','CSS','HTML']}
Object.assign
functionshallowClone(obj1){letobj2={}for(letiinobj1){obj2[i]=obj1[i]}returnobj2;}letobj1={name:'瑾行',list:['JS','CSS','HTML']}letobj2=shallowClone(obj1)obj2.name='七金'obj2.list[0]='Java'console.log(obj1,obj2)//{name:'瑾行',list:['Java','CSS','HTML']}//{name:'七金',list:['Java','CSS','HTML']}0
深拷贝
堆内存重新开辟全新的内存存放新对象,两个对象不会互相影响。
Array
序列化
利用JSON.stringify
将数组转为JSON字符串,再用JSON.parse
将字符串转为新数组。
functionshallowClone(obj1){letobj2={}for(letiinobj1){obj2[i]=obj1[i]}returnobj2;}letobj1={name:'瑾行',list:['JS','CSS','HTML']}letobj2=shallowClone(obj1)obj2.name='七金'obj2.list[0]='Java'console.log(obj1,obj2)//{name:'瑾行',list:['Java','CSS','HTML']}//{name:'七金',list:['Java','CSS','HTML']}1
Object
序列化
利用JSON.stringify
将对象转为JSON字符串,再用JSON.parse
将字符串转为新对象,但这个方法存在弊端。
functionshallowClone(obj1){letobj2={}for(letiinobj1){obj2[i]=obj1[i]}returnobj2;}letobj1={name:'瑾行',list:['JS','CSS','HTML']}letobj2=shallowClone(obj1)obj2.name='七金'obj2.list[0]='Java'console.log(obj1,obj2)//{name:'瑾行',list:['Java','CSS','HTML']}//{name:'七金',list:['Java','CSS','HTML']}2
貌似看起来没有任何问题,也不用引入lodash
库。那么,现在给对象添加个方法看看~
functionshallowClone(obj1){letobj2={}for(letiinobj1){obj2[i]=obj1[i]}returnobj2;}letobj1={name:'瑾行',list:['JS','CSS','HTML']}letobj2=shallowClone(obj1)obj2.name='七金'obj2.list[0]='Java'console.log(obj1,obj2)//{name:'瑾行',list:['Java','CSS','HTML']}//{name:'七金',list:['Java','CSS','HTML']}3
方法在JSON.stringify
后丢失了... 万事总有解决办法,实在不行引lodash
库。
我当时处理方法是将函数转为字符串确保不再丢失,最后再利用new Function()
去将字符串转为函数。
functionshallowClone(obj1){letobj2={}for(letiinobj1){obj2[i]=obj1[i]}returnobj2;}letobj1={name:'瑾行',list:['JS','CSS','HTML']}letobj2=shallowClone(obj1)obj2.name='七金'obj2.list[0]='Java'console.log(obj1,obj2)//{name:'瑾行',list:['Java','CSS','HTML']}//{name:'七金',list:['Java','CSS','HTML']}4
当然这个方法也并不是完美的,比如我确实有个字段为string
类型,且值就是function
,那就真是凑巧了。
这个现象引发了我对JSON.stringify
的兴趣,还会丢失哪些类型的数据?列举了写属性...
functionshallowClone(obj1){letobj2={}for(letiinobj1){obj2[i]=obj1[i]}returnobj2;}letobj1={name:'瑾行',list:['JS','CSS','HTML']}letobj2=shallowClone(obj1)obj2.name='七金'obj2.list[0]='Java'console.log(obj1,obj2)//{name:'瑾行',list:['Java','CSS','HTML']}//{name:'七金',list:['Java','CSS','HTML']}5
发现真的是深坑?♀️
函数、Symbol
、undefined
丢失
NaN
、Infinity
变null
了
RegExp
对象变{}
Date
对象转为字符串
继续在网上找JSON.stringify
踩坑文章,以下情况也得小心 5. 方法自带toJSON
, 直接返回函数return
值
functionshallowClone(obj1){letobj2={}for(letiinobj1){obj2[i]=obj1[i]}returnobj2;}letobj1={name:'瑾行',list:['JS','CSS','HTML']}letobj2=shallowClone(obj1)obj2.name='七金'obj2.list[0]='Java'console.log(obj1,obj2)//{name:'瑾行',list:['Java','CSS','HTML']}//{name:'七金',list:['Java','CSS','HTML']}6
属性引用自身,会报错
functionshallowClone(obj1){letobj2={}for(letiinobj1){obj2[i]=obj1[i]}returnobj2;}letobj1={name:'瑾行',list:['JS','CSS','HTML']}letobj2=shallowClone(obj1)obj2.name='七金'obj2.list[0]='Java'console.log(obj1,obj2)//{name:'瑾行',list:['Java','CSS','HTML']}//{name:'七金',list:['Java','CSS','HTML']}7
存在不可枚举属性,也会丢失
functionshallowClone(obj1){letobj2={}for(letiinobj1){obj2[i]=obj1[i]}returnobj2;}letobj1={name:'瑾行',list:['JS','CSS','HTML']}letobj2=shallowClone(obj1)obj2.name='七金'obj2.list[0]='Java'console.log(obj1,obj2)//{name:'瑾行',list:['Java','CSS','HTML']}//{name:'七金',list:['Java','CSS','HTML']}8
这些情况,有一些同样可以先转字符串,再转回原属性类型,算是一种思路吧,但都在对象value
情况可知的大前提下,不然还是用成熟的lodash
中的cloneDeep
吧。
深拷贝这么复杂,准备之后研究lodash
中的cloneDeep
源码,手写试试看,先起个草稿。 如果大家觉得有帮助,欢迎点赞、交流学习
作者:瑾行著作权归作者所有。