学习前端也有一段时间,最近会把一些面试常遇到的简单题总结下来,整合到脑子里~~~,在这记录一下,大家一起加油吧。
深拷贝,浅拷贝原理
js是没有引用传递的,只有值传递。但是js的数据类型分为基本数据类型(number,null,undefined,string)和引用数据类型(object,function)。基本数据类型的复制大家肯定没有什么异议,但是对于引用数据类型来说,我们可以这样理解,他在内存中存储的是数据存放的地址,而不是数据的值,真正数据存储的值是放在堆内存中。下面放一张盗图,哈哈哈:
下面我来解释一下,第一张图来说,一个个的小格格就是栈存储的表示方法,存放的是数据的值,每复制一个值,就在栈空间中再申请一个空间,加入值,即为复制成功;对于第二张图来说,以一个小格格中存放是的值的地址,每复制一个值,就在栈空间中申请一个新的空间,放入需要复制的值的地址,而这个地址和原始地址都指向相同的堆空间,所以可以理解的是,一个值变化,另一个值肯定也会跟着变啦~~~
简单理解
浅拷贝和深拷贝都只针对于引用数据类型,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存;但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象;
区别:浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制;
浅拷贝
方法一(遍历)
function simpleCopy(obj1){
var obj2 = Array.isArray(obj1)?[]:{};
for(let i in obj1){
obj2[i] = obj1[i];
}
return obj2;
}
//测试
var obj1 = [1,2,'3',{'4':4}]
var obj2 = simpleCopy(obj1)
console.log(obj1)
console.log(obj2)
obj1[3][4] = 5;
console.log(obj1)
console.log(obj2)
执行结果:
[ 1, 2, '3', { '4': 4 } ]
[ 1, 2, '3', { '4': 4 } ]
[ 1, 2, '3', { '4': 5 } ]
[ 1, 2, '3', { '4': 5 } ]
发现obj1的第三项和obj2的第三项是引用传递的。
方法二(object.assign)
function simpleCopy2(obj1){
var obj2 = Object.assign(obj1);
return obj2;
}
//测试
var obj1 = [1,2,'3',{'4':4}]
var obj2 = simpleCopy(obj1)
console.log(obj1)
console.log(obj2)
obj1[3][4] = 5;
console.log(obj1)
console.log(obj2)
执行结果:
[ 1, 2, '3', { '4': 4 } ]
[ 1, 2, '3', { '4': 4 } ]
[ 1, 2, '3', { '4': 5 } ]
[ 1, 2, '3', { '4': 5 } ]
深拷贝
方法一(JSON)
function deepClone(obj1){
return JSON.parse(JSON.stringify(obj1));
}
//测试
var obj1 = [1,2,'3',{'4':4},[1,2,3,[4,5]],"",{},NaN, undefined, null, true, function(){return 1}];
var obj2 = deepClone(obj1)
console.log(obj1)
console.log(obj2)
执行结果
[
1,
2,
'3',
{ '4': 4 },
[ 1, 2, 3, [ 4, 5 ] ],
'',
{},
NaN,
undefined,
null,
true,
[Function]
]
[
1,
2,
'3',
{ '4': 4 },
[ 1, 2, 3, [ 4, 5 ] ],
'',
{},
null,
null,
null,
true,
null
]
通过结果可以看出undefined,function,NaN在json的转化方法直接转换为null。
因为JSON.stringify()将值转换为相应的JSON格式时:(摘自MDN)
- undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略( 在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。函数、undefined 被单独转换时,会返回 undefined。
- 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
- Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理。
- NaN 和 Infinity 格式的数值及 null 都会被当做 null。
方法二:递归
function deepClone1(obj1){
var obj2 = Array.isArray(obj1)?[]:{};
if(obj1 && typeof obj1 === "object"){
for(var i in obj1){
if(obj1.hasOwnProperty(i)){
if(obj1[i] && typeof obj1[i] === "object"){
obj2[i] = deepClone1(obj1[i]);
}else{
obj2[i] = obj1[i];
}
}
}
}
return obj2;
}
//测试
var obj1 = [1,2,'3',{'4':4},[1,2,3,[4,5]],"",{},NaN, undefined, null, true, function(){return 1}];
var obj2 = deepClone1(obj1)
console.log(obj1)
console.log(obj2)
执行结果
[
1,
2,
'3',
{ '4': 4 },
[ 1, 2, 3, [ 4, 5 ] ],
'',
{},
NaN,
undefined,
null,
true,
[Function]
]
[
1,
2,
'3',
{ '4': 4 },
[ 1, 2, 3, [ 4, 5 ] ],
'',
{},
NaN,
undefined,
null,
true,
[Function]
]
结果正确。
缺陷:当遇到两个互相引用的对象,会出现死循环的情况,为了避免相互引用的对象导致死循环的情况,则应该在遍历的时候判断是否相互引用对象,如果是则退出循环。
方法三: 方法二的改进
function deepClone2(obj1){
var obj2 = Array.isArray(obj1)?[]:{};
if(obj1 && typeof obj1 === "object"){
for(var i in obj1){
var prop = obj1[i]
if(prop == obj1){
// console.log("prop:",prop)
obj2[i] = prop;//这个不知道加不加?
continue;
}
if(obj1.hasOwnProperty(i)){
if(obj1[i] && typeof obj1[i] === "object"){
obj2[i] = (prop.constructor === Array) ? [] : {};
arguments.callee(prop, obj2[i]); // 递归调用
}else{
obj2[i] = obj1[i];
}
}
}
}
return obj2;
}
//测试
var obj1 = [1,2,'3',{'4':4},[1,2,3,[4,5]],"",{},NaN, undefined, null, true, function(){return 1}];
obj1['a'] = obj1;
var obj2 = deepClone2(obj1)
console.log(obj1)
console.log(obj2)
输出结果
[
1,
2,
'3',
{ '4': 4 },
[ 1, 2, 3, [ 4, 5 ] ],
'',
{},
NaN,
undefined,
null,
true,
[Function],
a: [Circular]
]
[
1,
2,
'3',
{},
[],
'',
{},
NaN,
undefined,
null,
true,
[Function],
a: [
1,
2,
'3',
{ '4': 4 },
[ 1, 2, 3, [Array] ],
'',
{},
NaN,
undefined,
null,
true,
[Function],
a: [Circular]
]
]
好啦,到这里就结束啦。有什么需要可以再来问我哦。