堆和栈
栈:自动分配的内存空间,它由系统自动释放。存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配,主要存放以下几种数据,undefined, number, string, boolean, null
堆:动态分配的内存,大小不定也不会自动释放。放在堆内存中的对象,变量实际保存的是一个指针,这个指针指向另一个位置。每个空间大小不一样,要根据情况开进行特定的分配。
当我们需要访问引用类型(如对象,数组,函数等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。主要存放object类型的数据,比如function、object、array
等…
深拷贝和浅拷贝都是针对引用类型的值来说的。
浅拷贝
只是将数据存放的引用拷贝下来,指向的是同一片地址空间,所以其中一方改变数据,另一方也会跟着改变。
实现方法
Object.assign()
复制的对象第一层是深拷贝,第二层及以后每层都是浅拷贝。
//由于obj1的第一层name是深拷贝,所以不会随着obj3的改变而改变,但是info是第二层,里面是浅拷贝,会随着obj3的改变而改变
let obj1 = {
name: "zhangsan",
info:{
sex: "man",
friend: ["lisi", "wangmazi"]
}
};
let obj3 = Object.assign({}, obj1);
obj3.name = "zhongqw";
obj3.info.friend[1] = ["hah", "kek"];
console.log('obj1',obj1);
console.log('obj3',obj3);
2. Array.prototype.concat()
let arr1 = [1,[2,3],[4,5,6],{a: 7,b: 8}];
let arr2 = arr1.concat();
arr2[2] = [11,22,33];
arr2[3].b = 'hah';
arr2[2].push(0);
console.log(arr1);
console.log(arr2);
3. Array.prototype.slice()
let arr1 = [1,[2,3],[4,5,6],{a: 7,b: 8}];
let arr2 = arr1.slice();
arr2[2] = [44,55,66];
arr2[3].b = 'kek';
console.log(arr1);
console.log(arr2);
❓❓❓在这遗留一个问题:根据slice方法的MDN的返回值解释(concat()也有同样的问题)
?那么我arr1[2]里面[]和arr1[3]都是引用类型的,符合第一种情况。
但是在我改变arr2[2]的值时,arr1并没有发生改变;
在改变arr2[3]的值时,arr1也发生了改变???
深拷贝
将数据的引用和内容都重新拷贝一份,重新指向一个新的内存空间,此时两个数据互不影响。
实现方法
JSON.parse(JSON.stringify())
(该方法不可深度拷贝函数)
//obj1和obj2改变互不影响
function func(){
console.log('a');
}
let obj1 = [1,2,3,{a:4,b:5}, {func: func}];
let obj2 = JSON.parse(JSON.stringify(obj1));
obj2[0] = 0;
obj2.push(99);
obj2[3].a = 'new';
console.log('obj1',obj1);
console.log('obj2', obj2);
2. 原生实现深拷贝(可以拷贝函数)普通情况可用
function deepCopy(arr){
var obj = arr.constructor == Array?[]:{};
if(typeof arr!== 'object'){
return;
}
for(var item in arr){
if(typeof item === 'object') {
obj[item] = deepCopy(arr[item]);
}else{
obj[item] = arr[item];
}
}
return obj;
}
- 原生实现深拷贝对象内部有循环引用
- 父级引用
let obj1 = {
x: 1,
y: 2
}
obj1.z = obj1;
let obj2 = deepCopy(obj1);
解决方案:只需要判断一个对象的字段是否引用了这个对象或这个对象的任意父级即可
function deepCopy2(obj, parent=null) {
//创建一个新对象
let result = {};
let keys = Object.keys(obj),
key = null,
temp = null,
_parent = parent;
//该字段有父级则需要追溯该字段的父级
while(_parent) {
//如果该字段引用了它的父级,则为循环引用
if(_parent.originParent === obj) {
//循环引用返回同级的新对象
return _parent.currentParent;
}
_parent = _parent.parent
}
for(let i=0,len=keys.length;i<len;i++) {
key = keys[i]
temp = obj[key]
// 如果字段的值也是一个新对象
if(temp && typeof temp === 'object') {
result[key] = deepCopy(temp, {
//递归执行深拷贝,将同级的待拷贝对象与新对象传递给parent,方便追溯循环引用
originParent: obj,
currentParent: result,
parent: parent
});
} else {
result[key] = temp;
}
}
return result;
}
- 同级引用
let obj1 = {
a: {name: 'a'},
b: {name: 'b'},
c: {}
}
obj.c.d = obj.a;
console.log(obj.c.d === obj.a); //true
解决方案:只要记录下对象A中的所有对象,并与新创建的对象一一对应即可。
function deepCopy3(obj) {
// hash表,记录所有的对象的引用关系
let map = new WeakMap();
function dp(obj) {
let result = null;
let keys = Object.keys(obj);
let key = null,
temp = null,
existobj = null;
existobj = map.get(obj);
//如果这个对象已经被记录则直接返回
if(existobj) {
return existobj;
}
result = {}
map.set(obj, result);
for(let i =0,len=keys.length;i<len;i++) {
key = keys[i];
temp = obj[key];
if(temp && typeof temp === 'object') {
result[key] = dp(temp);
}else {
result[key] = temp;
}
}
return result;
}
return dp(obj);
}