美文网首页
一、如何实现对象深浅拷贝?

一、如何实现对象深浅拷贝?

作者: 你看Ta好像条狗啊 | 来源:发表于2018-04-12 14:01 被阅读578次

浅拷贝

浅拷贝很容易,只需要使用赋值运算符(=)即可

var obj = { age: '12' };
var newObj = obj;
//此时将obj中的age属性值改变,会发现newObj中的age属性值跟着改变
obj.age = '13';  //obj: { age: '13' }  newObj: { age: '13'}

原因:浅拷贝只是对对象的引用地址进行了拷贝,并没有开辟新的堆栈,拷贝后,两个对象指向的是同一个引用地址,所以修改其中一个对象的属性,则另一个对象的属性也会改变。

网上有流传Object.assign能够实现对象的深拷贝、sliceconcat能够实现数组的深拷贝,经过测试,这些都只是披着深拷贝的外衣的浅拷贝,如何理解这句话呢,如果该对象属性的值并没有引用类型时,这三个方法均能实现深拷贝。一旦对象属性的值为引用类型时,只会对该值的引用地址进行拷贝,这就回到的我上边所说的浅拷贝的问题。
tips: slice和concat只能实现数组, Object.assign实现对象

var obj = { age: '11', parent: { father: 'XX', mother: 'XX' } };
var newObj = Object.assign({}, obj);  //将obj中的参数复制到{}中,故参数顺序不能反
obj.age = '12';
obj.parent['father'] = 'YY';
console.log(obj, newObj);  //obj: { age: '12', parent: { father: 'YY', mother: 'XX' } } newObj: { age: '11', parent: { father: 'YY', mother: 'XX' }

深拷贝

那么如何实现真正的深拷贝呢?
1、JSON.parse方法(参照作者:寻找海蓝96,来源:https://juejin.im/post/5abb55ee6fb9a028e33b7e0a)
网上流传的一个黑科技,使用var newObj = JSON.parse(JSON.stringify(obj))可以实现对象的深拷贝,但是该方法存在一些问题:

1、无法作用于函数、RegExp等特殊对象,不过对函数进行深拷贝,我觉得并不需要,如果有需要对函数进行深拷贝用途的地方,希望大家不吝赐教,在评论区点出,让我这个萌新也能多了解了解。

2.会抛弃对象的constructor,所有的构造函数会指向Object.

3.对象有循环引用,会报错

主要的坑就是以上几点,我们一一测试下.

// 构造函数
function person(pname) {
  this.name = pname;
}

const Messi = new person('Messi');

// 函数
function say() {
  console.log('hi');
};

const oldObj = {
  a: say,
  b: new Array(1),
  c: new RegExp('ab+c', 'i'),
  d: Messi
};

const newObj = JSON.parse(JSON.stringify(oldObj));

// 无法复制函数
console.log(newObj.a, oldObj.a); // undefined [Function: say]
// 稀疏数组复制错误
console.log(newObj.b[0], oldObj.b[0]); // null undefined
// 无法复制正则对象
console.log(newObj.c, oldObj.c); // {} /ab+c/i
// 构造函数指向错误
console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: Object] [Function: person]

2、自定义方式实现深拷贝

/*利用递归来实现深拷贝,如果对象属性的值是引用类型(Array,Object),那么对该属性进行深拷贝,直到遍历到属性的值是基本类型为止。 */
function deepClone(obj){    
  //因为null是基础类型,但是用typeof判断时,显示为object
  //原理是因为是使用二进制判断,前三位为0则为object,而null,所有位数均为0,故显示为object
  if(obj === null) return null;
  if(typeof obj !== 'object') return obj;
  if(obj.constructor===Date) return new Date(obj); 
  if(obj.constructor === RegExp) return new RegExp(obj);
  var newObj = new obj.constructor ();  //保持继承链
  for (var key in obj) {
      if (obj.hasOwnProperty(key)) {   //不遍历其原型链上的属性
          var val = obj[key];
          newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; // 使用arguments.callee解除与函数名的耦合
      }
  }  
  return newObj;
}

tips: 该方法无法实现function的深拷贝!!!

3、更精确的自定义方式实现深拷贝

const isType = (obj, type) => {
  if (typeof obj !== 'object') return false;
  const typeString = Object.prototype.toString.call(obj);
  let flag;
  switch (type) {
    case 'Array':
      flag = typeString === '[object Array]';
      break;
    case 'Date':
      flag = typeString === '[object Date]';
      break;
    case 'RegExp':
      flag = typeString === '[object RegExp]';
      break;
    default:
      flag = false;
  }
  return flag;
};
/**
* deep clone
* @param  {[type]} parent object 需要进行克隆的对象
* @return {[type]}        深克隆后的对象
*/
const clone = parent => {
  // 维护两个储存循环引用的数组
  const parents = [];
  const children = [];

  const _clone = parent => {
    if (parent === null) return null;
    if (typeof parent !== 'object') return parent;

    let child, proto;

    if (isType(parent, 'Array')) {
      // 对数组做特殊处理
      child = [];
    } else if (isType(parent, 'RegExp')) {
      // 对正则对象做特殊处理
      child = new RegExp(parent);
      if (parent.lastIndex) child.lastIndex = parent.lastIndex;
    } else if (isType(parent, 'Date')) {
      // 对Date对象做特殊处理
      child = new Date(parent.getTime());
    } else {
      // 处理对象原型
      proto = Object.getPrototypeOf(parent);
      // 利用Object.create切断原型链
      child = Object.create(proto);
    }

    // 处理循环引用
    const index = parents.indexOf(parent);

    if (index != -1) {
      // 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象
      return children[index];
    }
    parents.push(parent);
    children.push(child);

    for (let i in parent) {
      // 递归
      child[i] = _clone(parent[i]);
    }

    return child;
  };
  return _clone(parent);
};

tips: 对Buffer对象、Promise、Set、Map这些对象还未做特殊处理!!!

相关文章

网友评论

      本文标题:一、如何实现对象深浅拷贝?

      本文链接:https://www.haomeiwen.com/subject/lhtxkftx.html