基本类型
基本数据类型有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']}1Object
序列化
利用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源码,手写试试看,先起个草稿。 如果大家觉得有帮助,欢迎点赞、交流学习
作者:瑾行著作权归作者所有。