前言
深浅拷贝也是面试经常问到的问题。能够体现面试者的基本功,代码能力,逻辑能力。
浅拷贝
拷贝就是复制的意思,如果你有一个a数组,拷贝a数组的时候会重新开辟一个空间,存放的东西和a数组里面的东西是一样的,但是他们的地址却不一样。
什么是浅拷贝
什么是浅拷贝,我们看一个栗子。
var a=[1,2,{a:5}];
var b=a.slice();
console.log(b);
上面定义一个a数组,通过slice方法拷贝a数组,用b去接收他。
- 第一个框框是打印b数组的结果,可以发现完全一模一样。
- 第二个框框说明对a数组修改后b数组没有改变,说明两个数组的地址不是同一个地址,相互之间没有影响
- 第三个框的操作是让a[2]的值再变回来
- 第四个框我们修改a【2】={a:5}里面的值,打印的结果发现b也改变了,这是为什么呢?地址不是一样的啊,那就不指向同一个引用啊?我们接着看第二张图。
其中&001等表示地址。如果你对引用值内存熟悉的话很容易就能看明白这个图。a[2]和b[2]存放的是对象{a:5}的地址,所以改变了会互相影响。这就是浅拷贝
浅拷贝的方法
1.手动写一个拷贝函数
var shollowColon=function(target){
//1.如果是引用类型要构造一个新的数组或者对象
var cloneResult=typeof target=="object"&&target&&Array.isArray(target)?[]:{};
for(props in target){
if(target.hasOwnProperty(props))
cloneResult[props] = target[props];
}
//2.引用值返回新的,非引用值返回原来的
return cloneResult || target;
}
说明: if(target.hasOwnProperty(props))用来确定某属性是否是对象本身的属性。假如我们在原型上增加一个sex属性,看下面代码
<script>
var shollowColon=function(target){
//1.如果是引用类型要构造一个新的数组或者对象
var cloneResult=typeof target=="object"&&target&&Array.isArray(target)?[]:{};
for(props in target){
// if(target.hasOwnProperty(props))
cloneResult[props] = target[props];
}
//2.引用值返回新的,非引用值返回原来的
return cloneResult || target;
}
var a = [1,2,{name:'linglong'}];
Array.prototype.sex="gir";
var b = shollowColon(a);
console.log(b);
</script>
打印a,b的结果是下面这个样子
2.object.assign方法
3.slice方法
4.扩展运算符…
5.concat方法
手动实现深拷贝
1.利用json下的字符串和对象转换方法
function deepClone(obj){
let _obj = JSON.stringify(obj),
objClone = JSON.parse(_obj);
return objClone
}
let a=[0,1,[2,3],4],
b=deepClone(a);
a[0]=1;
a[2][0]=1;
console.log(a,b);
但是该方法存在三个问题
- 无法解决循环引用问题
- 不能拷贝特殊的对象,如正则,date,set、map等。
- 不能拷贝函数
原因:它会抛弃对象的constructor,深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object;这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,也就是说,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON
手动实现
递归浅拷贝
var deepColon=function(target){
if(typeof target=="object"&&target){
//1.如果是引用类型要构造一个新的数组或者对象
var cloneResult=Array.isArray(target)?[]:{};
for(props in target){
if(target.hasOwnProperty(props))
// cloneResult[props] = target[props];
cloneResult[props] = deepColon(target[props]);
}
}
//2.引用值返回新的,非引用值返回原来的
return cloneResult || target;
}
递归+浅拷贝实现深拷贝存在的问题
- 爆栈问题
- 循环引用
- 循环检测
遍历树
var a = {
a1: 1,
a2: {
b1: 1,
b2: {
c1: 1
}
}
}
======================
a
/ \
a1 a2
| / \
1 b1 b2
| |
1 c1
|
1
function cloneLoop(x) {
const root = {};//拷贝后的结果
// 栈
const loopList = [
{
parent: root,
key: undefined,
data: x,
}
];
while(loopList.length) {
// 深度优先
const node = loopList.pop();//取出尾元素直到为空
const parent = node.parent;//存放拷贝结果
const key = node.key;
const data = node.data;//原拷贝对象
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}
for(let k in data) {
if (data.hasOwnProperty(k)) {//k是否是data对象上本身的属性
if (typeof data[k] === 'object') {
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}
return root;
}
- set和map
const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null;
const deepClone = (target, map = new Map()) => {
if(map.get(target))
return target;
if (isObject(target)) {
map.set(target, true);
const cloneTarget = Array.isArray(target) ? []: {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop],map);
}
}
return cloneTarget;
} else {
return target;
}
}