完美解决JavaScript的深浅拷贝

2020-09-20 19:57:11 浏览数 (1)

前言

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

正文

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

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

总结:

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

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

代码语言:javascript复制
let school={'name':"小丑"};
let my = {age:{count:18},name:"小丑的小屋"};
let all = {...school,...my};
my.age.count=100;
console.log(all);
console.log(my);

结果:

代码语言:javascript复制
{ age: { count: 100 }, name: '小丑的小屋' }
{ age: { count: 100 }, name: '小丑的小屋' }

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

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

代码语言:javascript复制
const _ = require("loadsh")
let my = {age:{count:18},name:"小丑的小屋"};
let all = _.cloneDeep(my);
all.age.count =100;
console.log(my);
console.log(all);

结果:

代码语言:javascript复制
{ age: { count: 18 }, name: '小丑的小屋' }
{ age: { count: 100 }, name: '小丑的小屋' }

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

拷贝的方法

1.数组方法[1]:slice和concat

  • slice
代码语言:javascript复制
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 ]

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

代码语言:javascript复制
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
代码语言:javascript复制
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 ]

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

代码语言:javascript复制
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 } ]

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

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

2.Object.assgin()

代码语言:javascript复制
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}  ✔
代码语言:javascript复制
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())
代码语言:javascript复制
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}}   ✔
代码语言:javascript复制
let school={'name':"小丑的小屋",fn:function(){}};
let my = {age:{count:18},name:"小丑的小屋"};
let all=JSON.parse(JSON.stringify({...school,...my}))
console.log(all);  //{'name':"小丑的小屋",age:{count:18}}; //❌把fn给丢了

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

  • 4.手写深拷贝
代码语言:javascript复制
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;
}
代码语言:javascript复制
let obj1 = {name:{age:"10"}}
let n = deepClone(obj1)
obj1.name.age = "231"
console.log(n);  //{name:{age:"10"}}  ✔
代码语言:javascript复制
let obj = { name:"小丑的小屋" }
obj.aaa=obj
let n = deepClone(obj1)
console.log(n);  //死循环了  ❌

解决这个问题可以使用WeakMap[2]

代码语言:javascript复制
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

代码语言:javascript复制
源码地址:https://github.com/lodash/lodash/blob/86a852fe763935bb64c12589df5391fd7d3bb14d/.internal/baseClone.js
  • 6.vue-router源码中的克隆方法
代码语言:javascript复制
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
  }
}
代码语言:javascript复制
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)] ]
代码语言:javascript复制
function extend (a, b) {
  for (const key in b) {
    a[key] = b[key]
  }
  return a
}
代码语言:javascript复制
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 } }   浅拷贝

参考资料

[1]

数组方法: https://www.cnblogs.com/baiyangyuanzi/p/6518218.html

[2]

WeakMap: https://es6.ruanyifeng.com/#docs/set-map

0 人点赞