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