完美解决JavaScript的深浅拷贝

2020-08-27 10:01:13 浏览数 (1)

前言

"拷贝"一直都是面试的热门考题。看似简单,实则难住不少面试者,回答的马马虎虎,模棱两可。抽出时间好好分析总结一下"拷贝",让这个难题彻底消失。

正文

从一则故事讲起,昨天因为医院开不出药,我拿上药取小区药店去买药,进门之后我问老板有没有这个药,老板转身进去一个小屋子拿了一盒药,果不其然确实有,药的名字和毫克一摸一样,但是盒子的样子和厂商不一样,我问老板:“这两个药是一种药吗,盒子不一样啊,药的成分是一样的吗?”老板说当然一样啊,这个就和你去买猪肉一样,同样是猪身上的肉,只不过是你去这个超市和去其他超市买场地一样而已。最后为了安全起见,我还是没有买那个药。

"拷贝"分为浅拷贝和深拷贝。它是针对对象来说的,如果不是对象一切免谈。这里的对象可以理解为我拿的那盒药,浅拷贝可以理解为老板拿出来的那盒药,虽然药的名字和毫克一样,然后里面的我们不知道是否真的一样,可能一样可能不一样。深拷贝可以理解为我买到了一摸一样的药,一层一层的药名,毫克,厂商,成分都一样。

总结:

  • 浅拷贝就是针对对象的属性依次进行复制,只复制一层,不会递归到个属性复制,会产生引用问题即内存地址是指的同一地址。简单来说就是拷贝之后和原对象有关
  • 深拷贝就是针对对象的各属性递归复制到新的对象上,内存地址不会指向同一地址。简单来说就是拷贝之后和元对象无关

下面看一个浅拷贝的例子:

let school={'name':"W3Cschool"};
let my = {age:{count:18},name:"W3Cschool字节宝"};
let all = {...school,...my};
my.age.count=100;
console.log(all);
console.log(my);

结果:

{ age: { count: 100 }, name: 'W3Cschool字节宝' }
{ age: { count: 100 }, name: 'W3Cschool字节宝' }

结论是:浅拷贝修改拷贝之后的对象上的属性会把原对象身上的属性同时修改掉。

下面再看一个深拷贝的例子:

const _ = require("loadsh")
let my = {age:{count:18},name:"W3Cschool字节宝"};
let all = _.cloneDeep(my);
all.age.count =100;
console.log(my);
console.log(all);

结果:

{ age: { count: 18 }, name: 'W3Cschool字节宝' }
{ age: { count: 100 }, name: 'W3Cschool字节宝' }

结论是:深拷贝修改拷贝之后的对象上的属性不会把原对象身上的属性同时修改掉。

拷贝的方法

1.数组方法:slice和concat

  • slice

let arr = [1,2,3,4];
let arr2 = arr.slice(0)
arr2[2]=5;


console.log(arr);  //[ 1, 2, 3, 4 ]
console.log(arr2); //[ 1, 2, 5, 4 ]

当数组里是不是对象的时候从结果上看是深拷贝,在看下面例子

let arr = [{1:1,2:2}];
let arr2 = arr.slice(0)
arr2[2]=5;


console.log(arr);  //[ { '1': 1 }, { '2': 5 } ]
console.log(arr2); //[ { '1': 1 }, { '2': 5 } ]

当数组里是对象的时候就变成了浅拷贝

  • concat

let arr = [1,2,3,4];
let arr2 = [].concat(arr);
arr2[2]=5;
console.log(arr); //[ 1, 2, 3, 4 ]  ✔
console.log(arr2); //[ 1, 2, 5, 4 ]

当数组里不是对象的时候从结果上看是深拷贝,在看下面例子

let arr = [{1:1},{2:2}];
let arr2 = arr.cancat(0)
arr2[1][2]=5;


console.log(arr);  //[ { '1': 1 }, { '2': 5 } ]  ❌变成了引用
console.log(arr2); //[ { '1': 1 }, { '2': 5 } ]

当数组里是对象的时候就变成了浅拷贝

总结:只有当数组是一维数组而且不包含对象的时候才是深拷贝

(推荐教程:JavaScript教程

2.Object.assgin()

let a= {a:1,b:2};
let b= Object.assign({},a);
a.a=3;
console.log(a)  //{a: 3, b: 2}
console.log(b)  //{a: 1, b: 2}  ✔
let a= {a:1,b:{c:2}};
let b= Object.assign({},a);
a.b.c=3;
console.log(a)  //{a: 1, b: {c:3}}
console.log(b)  //{a: 1, b: {c:3}}   ❌变成了引用

总结:Object.assgin如果涉及到嵌套多个对象的话就变成了引用 解决方法:使用JSON.stringify()先转化成字符串,再通过JSON.parse()转化成对象

  • 3.JSON.parse(JSON.stringify())

let a= {a:1,b:{c:2}};
let b= JSON.parse(JSON.stringify(a))
a.b.c=3;
console.log(a)  //{a: 1, b: {c:3}}
console.log(b)  //{a: 1, b: {c:2}}   ✔
let school={'name':"W3Cschool字节宝",fn:function(){}};
let my = {age:{count:18},name:"W3Cschool字节宝"};
let all=JSON.parse(JSON.stringify({...school,...my}))
console.log(all);  //{'name':"W3Cschool字节宝",age:{count:18}}; //❌把fn给丢了

总结: JSON.parse(JSON.stringify()) 这个方法有一定的局限性,会丢失 fn 。

  • 4.手写深拷贝

let deepClone=(obj)=>{
    if(obj==undefined) return obj;  //undefined == null
    if(obj instanceof RegExp) return new RegExp(obj);
    if(obj instanceof Date) return new Date(obj);
    if(typeof obj!=="object") return obj;
    let newObj = new obj.constructor;
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            newObj[key] = deepClone(obj[key])
        }
    }
    return newObj;
}
let obj1 = {name:{age:"10"}}
let n = deepClone(obj1)
obj1.name.age = "231"
console.log(n);  //{name:{age:"10"}}  ✔
let obj = { name:"W3Cschool字节宝" }
obj.aaa=obj
let n = deepClone(obj1)
console.log(n);  //死循环了  ❌

解决这个问题可以使用WeakMap

let deepClone=(obj,hash=new WeakMap())=>{
    if(obj==undefined) return obj;  //undefined == null
    if(obj instanceof RegExp) return new RegExp(obj);
    if(obj instanceof Date) return new Date(obj);
    if(typeof obj!=="object") return obj;
    if(hash.has(obj)) return hash.get(obj);
    let newObj = new obj.constructor;
    hash.set(obj,newObj);


    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            newObj[key] = deepClone(obj[key],hash)
        }
    }
    return newObj;
}

  • 5.lodash的cloneDeep


<br> 源码地址:https://github.com/lodash/lodash/blob/86a852fe763935bb64c12589df5391fd7d3bb14d/.internal/baseClone.js
<br> ```

  • 6.vue-router源码中的克隆方法

function clone (value) {
  if (Array.isArray(value)) {
    return value.map(clone)
  } else if (value && typeof value === 'object') {
    const res = {}
    for (const key in value) {
      res[key] = clone(value[key])
    }
    return res
  } else {
    return value
  }
}
let arr = [{1:1},{2:2},function(){}];
let arr2 = clone(arr)
arr2[1][2]=5;
console.log(arr)  //[ { '1': 1 }, { '2': 2 }, [Function (anonymous)] ]   ✔ 深拷贝
console.log(arr2); //[ { '1': 1 }, { '2': 5 }, [Function (anonymous)] ]
function extend (a, b) {
  for (const key in b) {
    a[key] = b[key]
  }
  return a
}
let b={a:1,b:{c:2}};
let a= extend({},b);
a.b.c=5;
console.log(a);  //{ a: 1, b: { c: 5 } }
console.log(b);  //{ a: 1, b: { c: 5 } }   浅拷贝

(推荐微课:JavaScript微课

文章来源:公众号--小丑的小屋 作者:小丑

以上就是W3Cschool字节宝关于 完美解决JavaScript的深浅拷贝 的相关介绍了,希望对大家有所帮助。

0 人点赞