手写深拷贝

简介: 手写深拷贝

偶然发现自己欠了一篇文章,那么今天就来自己动手实现一个深拷贝


之前的文章(《赋值、浅拷贝与深拷贝》)我们讲过深浅拷贝的概念、区别以及JSON.parse(JSON.stringify(obj))实现深拷贝存在的问题:


对于undefined,函数,Symbol会直接忽略


new Date()转换后结果不正确


对于正则转换为{}


对于循环引用,会报错


说到底,想要实现深拷贝,就是浅拷贝加递归,也就是如果对象的属性值还是一个对象的话,再进行一次拷贝,话不多说,上代码:


首先定义一个供深拷贝使用的对象:


let obj1 = {
  name:'obj.name',
  un:undefined,
  nu:null,
  sy:Symbol(123),
  say:function(){
    console.log(this.name);
  },
  reg:/\d{6}/g,
  date:new Date(),
  child:{
    name:'child.name'
  }
}复制代码


可见如上对象的属性值包含了JSON.parse(JSON.stringify(obj))存在问题的所有数据类型,接下来让我们实现一个深拷贝并一一解决JSON.parse(JSON.stringify(obj))深拷贝存在的问题


首先我们讲,实现深拷贝,就是遍历对象的key,并将value赋给新的对象的key,如果原对象的属性值为对象,则递归调用深拷贝方法(这里指的属性值为对象指有自己属性的对象,区别于正则,Date对象等),于是有了如下第一版代码:


function isObject(obj) {
  return typeof obj === 'object' && obj != null
}
function deepCopy(source){
  // 判断如果参数不是一个对象,返回改参数
  if(!isObject(source)) return source;
  // 判断参数是对象还是数组来初始化返回值
  let res = Array.isArray(source)?[]:{};
  // 循环参数对象的key
  for(let key in source){
    // 如果该key属于参数对象本身
    if(Object.prototype.hasOwnProperty.call(source,key)){
      // 如果该key的value值是对象,递归调用深拷贝方法进行拷贝
      if(isObject(source[key])){
        res[key] = deepCopy(source[key]);
      }else{
        // 如果该key的value值不是对象,则把参数对象key的value值赋给返回值的key
        res[key] = source[key];
      }
    }
  }
  // 返回返回值
  return res;
};复制代码


然后用如下代码来比对该方法的成果:


let obj2 = deepCopy(obj1);
console.log(obj1);
console.log(obj2);
console.log(obj2.sy === obj1.sy)
obj2.name = 'obj2.name';
obj2.say();复制代码


查看控制台输出结果:



可见第一版方法对于Date,正则的拷贝变成了空对象,对于方法及Symbol的拷贝都是没有问题的,其实对于第一版方法中判断source[key]是否是对象的方法isObject,对于Date对象和正则也会返回true,而这两种对象再次递归调用深拷贝方法的时候,由于其没有可遍历的key,所以返回的就是初始化的{},找到了问题点,我们优化上面的方法如下:


function isObject(obj) {
  return Object.prototype.toString.call(obj) === '[object Object]'||Object.prototype.toString.call(obj) ==='[object Array]'
}
function deepCopy(source){
  // 判断如果参数不是一个对象,返回改参数
  if(!isObject(source)) return source;
  // 判断参数是对象还是数组来初始化返回值
  let res = Array.isArray(source)?[]:{};
  // 循环参数对象的key
  for(let key in source){
    // 如果该key属于参数对象本身
    if(Object.prototype.hasOwnProperty.call(source,key)){
      // 如果该key的value值是对象,递归调用深拷贝方法进行拷贝
      if(isObject(source[key])){
        res[key] = deepCopy(source[key]);
      }else{
        // 如果该key的value值不是对象,则把参数对象key的value值赋给返回值的key
        res[key] = source[key];
      }
    }
  }
  // 返回返回值
  return res;
};复制代码


再次用如下代码来比对该方法的成果:


let obj2 = deepCopy(obj1);
console.log(obj1);
console.log(obj2);
console.log(obj2.sy === obj1.sy)
obj2.name = 'obj2.name';
obj2.say();复制代码


查看控制台输出结果:



可见第二版的方法对于Date和正则的拷贝已经完全没有问题了,那么我们再处理最后一个问题:循环引用


调用深拷贝之前添加如下代码:


obj1.child.child= obj1.child;
复制代码

再次用如下代码来比对该方法的成果:


let obj2 = deepCopy(obj1);
console.log(obj1);
console.log(obj2);
console.log(obj2.sy === obj1.sy)
obj2.name = 'obj2.name';
obj2.say();复制代码

查看控制台输出结果:


会发现第二版的方法对于循环引用的对象不停地递归调用,然后就爆栈了


解决如上方法,只需要在递归深拷贝之前判断是否已经拷贝过该对象,是的话把该对象返回,不要再递归下去即可,采用ES6的WeakMap对象保存已经拷贝过的对象,修改

deepCopy方法如下:


function deepCopy(source,hash = new WeakMap()){
  // 判断如果参数不是一个对象,返回改参数
  if(!isObject(source)) return source;
  if(hash.has(source)) return hash.get(source); // 如果拷贝过该对象,则直接返回该对象
  // 判断参数是对象还是数组来初始化返回值
  let res = Array.isArray(source)?[]:{};
  hash.set(source,res); // 哈希表添加新对象
  // 循环参数对象的key
  for(let key in source){
    // 如果该key属于参数对象本身
    if(Object.prototype.hasOwnProperty.call(source,key)){
      // 如果该key的value值是对象,递归调用深拷贝方法进行拷贝
      if(isObject(source[key])){
        res[key] = deepCopy(source[key],hash);
      }else{
        // 如果该key的value值不是对象,则把参数对象key的value值赋给返回值的key
        res[key] = source[key];
      }
    }
  }
  // 返回返回值
  return res;
};
复制代码


再次用如下代码来比对该方法的成果:


let obj2 = deepCopy(obj1);
console.log(obj1);
console.log(obj2);
console.log(obj2.sy === obj1.sy)
obj2.name = 'obj2.name';
obj2.say();复制代码


查看控制台输出结果:



可见第三版方法对于对象的循环引用也可以完美拷贝了!


如果有错误或者不严谨的地方,请给予指正,十分感谢!

相关文章
|
7月前
|
存储 人工智能 前端开发
深拷贝浅拷贝的区别?如何实现一个深拷贝?
深拷贝浅拷贝的区别?如何实现一个深拷贝?
111 0
|
3月前
|
存储 JSON 前端开发
栈在前端中的应用,顺便再了解下深拷贝和浅拷贝!
该文章探讨了栈在前端开发中的应用,并深入讲解了JavaScript中深拷贝与浅拷贝的区别及其实现方法。
栈在前端中的应用,顺便再了解下深拷贝和浅拷贝!
|
7月前
|
JavaScript 前端开发
掌握手写深拷贝,轻松处理复杂对象的数据传递!
掌握手写深拷贝,轻松处理复杂对象的数据传递!
|
5月前
|
前端开发 JavaScript
前端深拷贝、浅拷贝,一起手撕深拷贝
【7月更文挑战第2天】JavaScript中的深拷贝和浅拷贝关乎对象复制的独立性。浅拷贝(如`Object.assign()`、扩展运算符)创建新对象,但共享引用类型属性的内存地址,导致修改新对象会影响原始对象。深拷贝(如递归复制)创建完全独立的对象副本,不受原始对象变动影响。`JSON.parse(JSON.stringify(obj))`是简单的深拷贝方法,但无法处理函数、undefined、Symbol及循环引用。手动实现深拷贝需递归遍历并处理循环引用问题,以确保复制的完整性。理解这两者差异对编写健壮的代码至关重要。
34 0
|
7月前
|
存储 Java Apache
【面试问题】深拷贝和浅拷贝的区别?
【1月更文挑战第27天】【面试问题】深拷贝和浅拷贝的区别?
|
JavaScript
一文弄懂浅拷贝和深拷贝
一文弄懂浅拷贝和深拷贝
54 0
|
存储 JavaScript 前端开发
深拷贝浅拷贝有什么区别?怎么实现深拷贝?
深拷贝浅拷贝有什么区别?怎么实现深拷贝?
104 0
|
存储 JSON 缓存
前端面试:浅拷贝和深拷贝的区别?
前端面试:浅拷贝和深拷贝的区别?
124 0
|
前端开发
前端学习案例10-深拷贝和浅拷贝
前端学习案例10-深拷贝和浅拷贝
77 0
前端学习案例10-深拷贝和浅拷贝
|
JavaScript 前端开发 Java
一文搞懂浅拷贝与深拷贝到底有什么区别
一文搞懂浅拷贝与深拷贝到底有什么区别
166 0