数据类型存储
基本类型数据保存在在栈内存中
引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中。
为什么基本数据类型存在栈内存,引用数据类型存在堆内存?
- 基本数据类型比较稳当,相对来说占用的内存较小
- 引用数据类型是动态的,大小不固定,占用的内存较大,但内存地址大小是固定的,因此可以将内存地址保存在栈中
浅拷贝和赋值(=)的区别
基本类型赋值,系统会为新的变量在栈内存中分配一个新值,这个很好理解。
引用类型赋值,系统会为新的变量在栈内存中分配一个值,这个值仅仅是指向同一个对象的引用,和原对象指向的都是堆内存中的同一个对象。
var obj1 = new Object();
var obj2 = obj1;
obj1.name = 'lucyStar';
console.log(obj2.name);
// lucyStar
我们可以看到,obj1保存了一个对象的实例,这个值被赋值到 Obj2中。赋值操作完成后,两个变量实际引用的是同一个对象,改变了其中一个,会影响另外一个值。
什么是浅拷贝?
如果是对象类型,则只拷贝一层,如果对象的属性又是一个对象,那么此时拷贝的就是此属性的引用。
简单实现一个浅拷贝
function shadowCopy(obj) {
const newObj = {};
for(let prop in obj) {
if(obj.hasOwnProperty(prop)){
newObj[prop] = obj[prop];
}
}
return newObj;
}
const obj1 = {
name: 'litterStar',
a: {
b: '1'
}
};
const obj2 = shadowCopy(obj1);
obj2.name = 'lucyStar';
obj2.a.b = '2';
console.log(obj1);
// { name: 'litterStar', a: { b: '2' } }
console.log(obj2);
// { name: 'lucyStar', a: { b: '2' } }
可以看到修改obj2
的name
属性不会影响 obj1
,但是修改 obj2
的 a属性(是个对象)的 b,就会影响 obj1.a.b
使用下面这些函数得到的都是浅拷贝:
Object.assign
-
Array.prototype.slice()
,Array.prototype.concat()
- 使用拓展运算符实现的复制
深拷贝
什么是深拷贝?
浅拷贝是只拷贝一层,深拷贝会拷贝所有的属性。深拷贝前后两个对象互不影响。
深拷贝的实现
JSON.parse(JSON.stringify())
- 手写递归函数
- 函数库lodash
JSON.parse(JSON.stringify())
有存在以下问题:
- 无法解决循环引用问题
- 无法拷贝特殊的对象,比如:RegExp, Date, Set, Map等在序列化的时候会丢失。
- 无法拷贝函数
let obj = {
fun: function(){},
syb: Symbol('foo'),
a: undefined,
b: NaN,
c: Infinity,
reg : /^abc$/,
date: new Date(),
set: new Set([1, 2, 3, 4, 4]),
map: new Map([
['name', '张三'],
['title', 'Author']
]),
text:'aaa',
}
let cloneObj = JSON.parse(JSON.stringify(obj));
console.log(cloneObj);
// 结果如下
{ reg: {},
b: null,
c: null,
date: '2020-05-05T09:32:52.533Z',
set: {},
map: {},
text: 'aaa'
}
console.log(typeof obj.date); // object
console.log(typeof cloneObj.date); // string
可以看到
- 如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;
- 如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象;
- 如果obj里有RegExp、 Set, Map等,则序列化的结果将只得到空对象;
- 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
- 循环引用,则直接报错
TypeError: Converting circular structure to JSON
const obj = {
a: '111',
}
obj.b = obj;
let cloneObj = JSON.parse(JSON.stringify(obj));
console.log(cloneObj); // TypeError: Converting circular structure to JSON
尝试自己写一个深拷贝,需要考虑下面这几种情况
- 属性是基本类型
- 属性是对象
- 属性是数组
- 循环引用的情况,比如
obj.prop1 = obj
function deepCopy(originObj, map = new WeakMap()) {
// 判断是否为基本数据类型
if(typeof originObj === 'object') {
// 判断是都否为数组
const cloneObj = Array.isArray(originObj) ? [] : {};
// 判断是否为循环引用
if(map.get(originObj)) {
return map.get(originObj);
}
map.set(originObj, cloneObj);
for(const prop in originObj) {
cloneObj[prop] = deepCopy(originObj[prop], map);
}
return cloneObj;
} else {
return originObj;
}
}
const obj1 = {
a: '111',
}
obj1.obj2 = obj1;
const aa = deepCopy(obj1);
console.log(aa);
// { a: '111', obj2: [Circular] }
上面的实现存在一些问题
- 一些特殊类型的对象,比如 Date, 正则,Set,Map等没有处理
- 使用typeof 来判断是否是对象是有问题的,typeof null 的结果也是 'object'
下面这一版本
function deepCopy(originObj, map = new WeakMap()) {
// 判断是否为基本数据类型
if(isObject(originObj)) {
// 判断是否为循环引用
if(map.get(originObj)) {
return map.get(originObj);
}
// 判断是否为几种特殊需要处理的类型
let type = [Date, RegExp, Set, Map, WeakMap, WeakSet];
if(type.includes(originObj.constructor)) {
return new originObj.constructor(originObj);
}
// 其他类型
let allDesc = Object.getOwnPropertyDescriptors(originObj);
let cloneObj = Object.create(Object.getPrototypeOf(originObj), allDesc);
// Reflect.ownKeys 可以获取到
for(const prop of Reflect.ownKeys(originObj)) {
cloneObj[prop] = isObject(originObj[prop]) && typeof originObj[prop] !== 'function' ? deepCopy(originObj[prop], map) : originObj[prop];
}
return cloneObj;
} else {
return originObj;
}
}
// 是否为引用类型
function isObject(obj) {
return typeof obj === 'object' || typeof obj === 'function' && obj !== null;
}
let obj = {
fun: function(){},
syb: Symbol('foo'),
a: undefined,
b: NaN,
c: Infinity,
reg : /^abc$/,
date: new Date(),
set: new Set([1, 2, 3, 4, 4]),
map: new Map([
['name', '张三'],
['title', 'Author']
]),
text:'aaa',
}
let cloneObj = deepCopy(obj);
console.log(cloneObj);
打印的结果如下
{ fun: [Function: fun],
syb: Symbol(foo),
a: undefined,
b: NaN,
c: Infinity,
reg: /^abc$/,
date: 2020-05-05T13:57:14.904Z,
set: Set { 1, 2, 3, 4 },
map: Map { 'name' => '张三', 'title' => 'Author' },
text: 'aaa'
}
上面只是实现一个简单的深拷贝,很多情况未考虑到,比如 特殊的数据类型及兼容性的处理,更多细节的实现可以参考 lodash 中的 cloneDeep方法。
总结
和原数据是否指向同一对象 | 第一层数据为基本数据类型 | 原数据中包含对象 | |
---|---|---|---|
赋值 | 是 | 改变会使原数据一同改变 | 改变会使原数据一同改变 |
浅拷贝 | 否 | 改变不会使原数据一同改变 | 改变会使原数据一同改变 |
深拷贝 | 否 | 改变不会使原数据一同改变 | 改变不会使原数据一同改变 |
备注:markdown 表格在 https://tableconvert.com/ 该网站生成,很方便。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。