JS的数据类型
基本数据类型:String,Number,Boolean,Symbol,Null,Undefined,BigInt
- 基本数据类型存放在栈中
引用数据类型:Object
- 引用数据存放在堆内存中,栈空间只是存放堆内存中的地址
赋值,浅拷贝和深拷贝之间的区别
赋值:
- 对于基本数据类型,完全复制了一份新值
- 对于引用类型是将A的引用地址拷贝给了B,但共用一个实体
浅拷贝和深拷贝只针对引用数据类型
浅拷贝:
创建一个新对象
- 属性是基本数据类型,拷贝的是基本数据类型,
- 属性是引用类型,拷贝的是内存地址(单层对象可以,多层对象不行)
// 浅拷贝
let obj = { a: 1, b: { c: 2 } };
// 使用扩展运算符进行拷贝
let obj2 = { ...obj };
obj2.a = 3;
obj2.b.c = 4;
console.log(obj); // {a:1, b: { c: 4 }}
console.log(obj2); // {a: 3, b: { c: 4 }}
浅拷贝存在一个问题,当对象里面包含对象时,修改多层对象中的值会影响原始数据,所以浅拷贝修改新复制对象的一层属性时,原数据不会发生改变。为了不影响原始数据所有属性,需要深拷贝
深拷贝:
层层递归拷贝, 修改基本数据类型和引用数据类型都不会影响原有的数据
浅拷贝常用方法
- Object.assign
- 扩展运算符
- slice
- concat
let obj = ['iyongbao', { vue: 98 }]
let obj2 = obj.slice();
let obj3 = obj.concat();
obj[0] = "zhangsan";
obj[1].vue = 60;
console.log(obj); // {name: "zhangsan", score: { vue: 60 }}
console.log(obj2); // {name: "iyongbao", score: { vue: 60 }}
console.log(obj3); // {name: "iyongbao", score: { vue: 60 }}
手写浅拷贝
const shallowClone = (target)=> {
// 对于基本数据类型,直接返回
if(typeof target !== 'object' || target === null) {
return target
}
let cloneTarget = Array.isArray(target) ? []:{}
Object.keys(target).forEach(item=> {
cloneTarget[item] = target[item]
})
return cloneTarget
}
深拷贝常用方法
- JSON.parse(JSON.stringify())
- loadash第三方库
- 手写
手写深拷贝
简洁版
const deepClone = (target) => {
// 对于基本数据类型,直接返回
if(typeof target !== 'object' || target == null){
return target
}
const cloneTarget = Array.isArray(target) ? [] : {}
// 递归调用
for (let key in target) {
// 只拷贝对象上显示具有的属性
if (target.hasOwnProperty(key)){
cloneTarget[key] = deepClone(target[key])
} else {
cloneTarget[key] = target[key]
}
}
return cloneTarget
}
存在一些问题: 循环引用,Date,RegExp,Symbol,Set,Map等类型不能正确深拷贝
解决循环引用
循环引用:子对象中又引用了父对象,对象中存在循环引用的情况,即对象的属性间接或者直接引用了自身的情况
const c = {name: "muyiy"};
c.test = c;
const d = deepClone(c); // 循环引用,栈溢出
用WeakMap,用来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间WeakMap中找,有没有拷贝过这个对象,如果有的话直接返回,没有的话继续拷贝
WeakMap:
WeakMap对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。当下一次垃圾回收机制执行时,这块内存就会被释放掉。
const deepClone = (target, map=new WeakMap()) => {
// 对于基本数据类型,直接返回
if(typeof target !== 'object' || target == null){
return target
}
// 处理循环引用,判断有没有拷贝这个对象,有的话直接返回
if(map.has(target)) return map.get(target)
const cloneTarget = Array.isArray(target) ? [] : {}
map.set(target, cloneTarget)
// 递归调用
for (let key in target) {
// 只拷贝对象上显示具有的属性
if (target.hasOwnProperty(key)){
cloneTarget[key] = deepClone(target[key])
} else {
cloneTarget[key] = target[key]
}
}
return cloneTarget
}
处理其他数据类型
Symbol类型
(1)for...of和for...in不能遍历Symbol类型数据,Object.keys()也会忽略
(2)Symbol可以作为对象属性
解决方法:
(1) Object.getOwnPropertySymbols(obj):遍历Symbol类型的属性
(2) Reflect.ownKeys(obj):返回对象的所有键,包括Symbol属性
(3) Symbol.prototype.valueOf():返回当前Symbol对象所包含的symbol包含值
const deepClone = (target, map=new WeakMap()) => {
// 对于基本数据类型,直接返回
if(typeof target !== 'object' || target == null){
return target
}
// 遍历Symbol类型的值
if(Object.prototype.toString.call(target).slice(8,-1) === 'Symbol'){
return Object(Symbol.prototype.valueOf.call(target))
}
// 循环引用,判断有没有拷贝这个对象,有的话直接返回
if(map.has(target)) return map.get(target)
const cloneTarget = Array.isArray(target) ? [] : {}
map.set(target, cloneTarget)
// 递归调用,遍历所有类型
Reflect.ownKeys(target).forEach(key=> {
if (target.hasOwnProperty(key)){
cloneTarget[key] = deepClone(target[key])
} else {
cloneTarget[key] = target[key]
}
})
return cloneTarget
}