数据存储
数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和对象数据类型(Object,Function)
基本数据类型的特点:数据直接存储在栈上
引用数据类型的特点:栈上存储的是对象的地址,实际数据存储在堆里
概念
-
赋值:
- 对于基本数据类型,赋值,赋值之后两个变量互不影响
- 对于引用数据类型,赋址,他们都会指向同一地址
- 对于基本数据类型,赋值,赋值之后两个变量互不影响
-
浅拷贝:
- 对于基本数据类型而言,拷贝的是值,
- 对于引用数据类型而言,拷贝的是对象的地址,结果是两个对象都会指向同一个地址
-
深拷贝:深拷贝会为对象开辟一个新的空间,来存储对象拷贝的值,因为拷贝的不是同一地址,所以不会相互影响各自的属性
下面是关于对象的深浅拷贝示意图
如何实现
浅拷贝
1. Object.assign
let a = {
age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
2. 展开运算符(…)
let a = {
age: 1
}
let b = {...a}
a.age = 2
console.log(b.age) // 1
深拷贝
1. JSON.parse(JSON.stringify(object))
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 - 不能序列化函数
- 不能解决循环引用的对象
- 不能正确处理
new Date() - 不能处理正则
证明
undefined、symbol和函数
let obj = {
name: 'sumuy',
a: undefined,
b: Symbol('Sumuyzzz'),
c: function() {}
}
console.log(obj);
// {
// name: "sumuy",
// a: undefined,
// b: Symbol(Sumuyzzz),
// c: ƒ ()
// }
let b = JSON.parse(JSON.stringify(obj));
console.log(b);
//{name:"sumuy"}
- 循环引用情况下,会报错。
let obj = {
a: 1,
b: {
c: 2,
d: 3
}
}
obj.a = obj.b;
obj.b.c = obj.a;
let b = JSON.parse(JSON.stringify(obj));
new Date情况下,转换结果不正确。
new Date();
JSON.stringify(new Date());
手写深拷贝
基础版本
function clone(target) {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = target[key];
}
return cloneTarget;
};
创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性依次添加到新对象上,返回。
考虑引用类型和原始类型
function clone(target) {
if (typeof target === 'object') {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
const target = {
field1: 1,
field2: undefined,
field3: 'Sumuyzzz',
field4: Symbol('sumuyzzz'),
field5: {
child: 'child',
child2: {
child2: 'child2'
}
},
field6:function(){},
};
let result = clone(target)
console.log(result)
执行结果:
考虑数组版本
function clone(target) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {}
for (const key in target) {
cloneTarget[key] = target[key]
}
return cloneTarget
} else {
return target
}
}
const target = {
field1: 1,
field2: undefined,
field3: 'Sumuyzzz',
field4: Symbol('sumuyzzz'),
field5: {
child: 'child',
child2: {
child2: 'child2'
}
},
field6:function(){},
field7: [1, 2, 4, 8]
};
let result = clone(target)
console.log(result)
执行结果
考虑循环引用
解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。
function clone(target, map = new Map()) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {}
if (map.get(target)) {
return map.get(target)
}
map.set(target, cloneTarget)
for (const key in target) {
cloneTarget[key] = clone(target[key], map)
}
return cloneTarget
}
return target
}
const target = {
field1: 1,
field2: undefined,
field3: 'Sumuyzzz',
field4: Symbol('sumuyzzz'),
field5: {
child: 'child',
child2: {
child2: 'child2'
}
},
field6: function () {},
field7: [1, 2, 4, 8]
};
target.target = target;
const result = clone(target);
console.log(result);
总结
赋值:当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。
let obj ={
a:1,
b:{
c:2,
d:3,
}
}
let newObj = obj
console.log(newObj) //{a:1,b{...}}
console.log(obj === newObj) //true
浅拷贝:创建一个新的对象,如果属性的基本类型,拷贝的就是值,如果是引用类型,拷贝的就是内存地址
let obj ={
a:1,
b:{
c:2,
d:3,
}
}
let newObj = {...obj}
console.log(newObj) //{a:1,b{...}}
console.log(obj === newObj) //false
console.log(obj.b === newObj.b) //true
深拷贝:深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
let obj ={
a:1,
b:{
c:2,
d:3,
}
}
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj) //{a:1,b{...}}
console.log(obj === newObj) //false
console.log(obj.b. === newObj.b) //false