包学不会的深拷贝与浅拷贝

深拷贝与浅拷贝的含义

首先要知道在js的世界中,有两种类型的值,分别是基础类型和引用类型。基础类型包括string,number,boolean,Symbol,undefined,null,引用类型包括array,object等。

  • 基本数据类型:直接存储在栈(stack)中的数据
  • 引用数据类型:在栈中存放的是指针,真实的数据存放在堆中。

首先要分清拷贝和赋值的区别
引用类型的赋值是直接赋值了一个指针,二者指向相同的数据内存。而拷贝是要创建一个新的对象/数组,如果只拷贝一层数据的话叫浅拷贝,如果拷贝多层数据叫深拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let obj={
name:'Mike',
age:36,
hobby:['movies','music','running']
}
// 这是赋值,任何一个都能操作共有的内存数据
let obj1=obj;
obj1.name='fred';
obj1.hobby[1]='eat';
console.log(obj,obj1);

// 这是浅拷贝,只能拷贝一层数据,当对象中有引用类型的value时,只会拷贝这个引用的指针
let obj2={...obj};
obj2.name='max';//不会改变源对象
obj2.hobby[1]='sleep'; //会改变源对象
console.log(obj,obj2)

如何实现浅拷贝

数组的浅拷贝

  • slice方法

    1
    2
    3
    4
    let arr=[1,5,9],
    arr1=arr.slice();
    arr1.push(11);
    console.log(arr,arr1)
  • concat方法,当不给concat传参时,该方法和slice()作用相同

    1
    2
    let arr1=arr.concat();
    ...
  • spread操作符

    1
    2
    3
    4
    5
    6
    let arr=[1,3,5,[8,9]];
    let arr1=[...arr];
    arr.push(15);
    console.log(arr,arr1);
    arr1[3].push(18);
    console.log(arr,arr1);

对象的浅拷贝

  • 手动实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function shallowCopy(src){
    var target={};
    for(let key in src){
    if(src.hasOwnProperty(key)){
    target[key]=src[key];
    }
    }
    return target
    }
    // hasOwnProperty方法可以排除来自原型链上的属性
  • Object.assign()

    1
    2
    3
    4
    let obj={a:{name:'weifo',age:33}};
    let copyobj=Object.assign({},obj);
    copyobj.a.name='curt';
    concole.log(obj.a.name);//curt
  • spread

    1
    2
    3
    let obj={name:'weifo',skill:['english','code']};
    let copyobj={...obj,name:'fred'};//{name:'fred',skill:[...]}
    //es6语法

深拷贝的实现

  • trick版

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    JSON.parse(JSON.stringify(obj))
    // 该方法的局限性:symbol,undefined,函数的属性值会被忽略,例子如下
    let syb=Symbol(12);
    let obj={
    name:'fred',
    say:function(){
    alert('It wont alert');
    },
    syb:syb,
    age:undefined
    }
    let copy=JSON.parse(JSON.stringify(obj));
    console.log(copy);//{name:'fred'}
  • 手动实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function deepclone(src){
    if(typeof src==='object'){
    let clone=Array.isArray(src)?[]:{};
    for(let key in src){
    clone[key]=deepclone(src[key]);
    }
    return clone
    }else{
    return src;
    }
    }
    let test={
    name:'fred',
    cities:['tokyo','paris','london']
    }
    let clone=deepclone(test);
    clone.cities.push('rome');
    clone.name='weifo';
    console.log(test,clone)
  • 解决循环引用
    上面的方法可以满足大部分的应用场景了,除了遇到对象引用自身的情况,如果采用上面的方法,会有爆栈的错误提示。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const test={
    name:'weifo',
    field:[3,5,11],
    field1:{
    child:'name'
    }
    }
    test.test=test;//循环引用
    deepclone(test)//RangeError

为了解决循环引用的问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝对象时,先去存储对象中找,找到的话直接返回,没有则继续拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function deepclone(target,map=new Map()){
if(typeof target==='object'){
let clone=Array.isArray(src)?[]:{};
if(map.get(target)){
return target
}
map.set(target,clone);
for(let key in target){
clone[key]=clone(target[key],map);
}
return clone
}else{
return target
}
}

  • 使用lodash
    1
    2
    3
    4
    5
    var _=require('lodash');
    var objects = [{ 'a'1 }, { 'b'2 }];
     
    var deep = _.cloneDeep(objects);
    console.log(deep[0] === objects[0]);//false