Javascript深拷贝实现

如何实现深拷贝

Object.assign()

只能处理一层的深拷贝

var oldObj = {a:1};
var newObj = Object.assign({},oldObj)
console.log(oldObj===newObj)//false
序列/反序列

无法实现对函数 、RegExp 等特殊对象的克隆

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

对象有循环引用,会报错

这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构

// 构造函数
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]
递归拷贝
function isArray(arr) {
  return Object.prototype.toString.call(arr) == '[object Array]'
};

function _clone(oldObj) {
  console.log(typeof oldObj)
  if (typeof oldObj !== "object") {
      return oldObj;
  }
  var obj = isArray(oldObj) ? [] : {}
  for (var i in oldObj) {
      if (oldObj.hasOwnProperty(i)) {
          if (oldObj[i] == oldObj) {
              continue
          }
          obj[i] = _clone(oldObj[i])
      }
  }
  return obj
}
//还是用上面的测试
function Person(pname) {
this.name = name;
}
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 = _clone(oldObj)
// 函数可复制
console.log(newObj.a, oldObj.a); // [Function: say] [Function: say]
// 稀疏数组可以复制
console.log(newObj.b[0], oldObj.b[0]); // undefined undefined
// 无法复制正则对象
console.log(newObj.c, oldObj.c); // {} /ab+c/i
// 构造函数指向错误
console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: Object] [Function: person]
//循环引用也不行,上面的continue去掉会内存溢出

同样存在无法复制正则对象,构造函数指向错误等问题。

递归拷贝 改进版
   const isType = function (obj, type) {
  //用来区别属性类型的函数
  if (typeof obj !== 'object') return false
  const typeString = Object.prototype.toString.call(obj);
  let flag;
  //flag作为属性判断的标识
  switch (type) {
    case 'Array':
      flag = typeString === '[object Array]';
      break;
    case 'Date':
      flag = typeString === '[object Date]';
      break;
    case 'RegExp':
      flag = typeString === '[object RegExp]';
      break;
    case 'Set':
      flag = typeString === '[object Set]';
      break;
    case 'Map':
      flag = typeString === '[object Map]';
      break;
    case 'Symbol':
      flag = typeString === '[object Symbol]';
      break;
    default:
      flag = false;
  }
  return flag;
}

function _clone(oldObj) {
  let obj;
  if (isType(oldObj) == false) {
    //基本数据类型,函数,和我没考虑到的类型,一律直接引用(偷懒)
    obj = oldObj
  } else if (isType(oldObj, 'Array')) {
    //处理数组
    obj = [];
  } else if (isType(oldObj, 'RegExp')) {
    //处理正则
    let flags = '';
    if (oldObj.global) flags += "g";//全局
    if (oldObj.ignoreCase) flags += "i";//大小写
    if (oldObj.multiline) flags += "m";//
    return obj = new RegExp(oldObj.source, getRegExp(oldObj));
  } else if (isType(oldObj, 'Date')) {
    //处理时间对象
    return obj = new Date(oldObj.getTime());
  } else if (isType(oldObj, 'Set')) {
    //处理Set
    return obj = new Set([...obj]);
  } else if (isType(oldObj, 'Map')) {
    //处理Map
    return obj = new Map([...obj]);
  } else if (isType(oldObj, 'Symbol')) {
    //处理Symbol
    return obj = Symbol(String(oldObj).slice(7, -1))
  } else {
    // 处理对象原型
    let proto = Object.getPrototypeOf(oldObj);
    obj = Object.create(proto);
  }
  for (var i in oldObj) {
    if (oldObj.hasOwnProperty(i)) {
      if (oldObj[i] == oldObj) {
        obj[i] = oldObj
      } else {
        obj[i] = _clone(oldObj[i])
      }
    }
  }
  return obj
}

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,
    e:new Set([1,2,3]),
    f:new Map([['foo', 1],['bar', 2]]),
    g:Symbol("yosgi")
};
const newObj = _.clone(oldObj)
// 函数
console.log(newObj.a, oldObj.a); // [Function: say] [Function: say]
// 稀疏数组
console.log(newObj.b[0], oldObj.b[0]); // undefined undefined
// 正则对象
console.log(newObj.c, oldObj.c); // /ab+c/i /ab+c/i
// 构造函数
console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: person] [Function: person]
//Set
console.log(newObj.e, oldObj.e)//Set(3) {1, 2, 3} Set(3) {1, 2, 3}
//Map
console.log(newObj.f, oldObj.f)//Map(2) {"foo" => 1, "bar" => 2} Map(2) {"foo" => 1, "bar" => 2}
//Symbol
console.log(newObj.g, oldObj.g)//Symbol(yosgi) Symbol(yosgi)
//循环引用
// var oldObj = {};;
// oldObj.a = oldObj;
// const newObj = _.clone(oldObj)
// console.log(oldObj,newObj)//{a: {…}} {a: {…}}

函数,数组,正则对象,构造函数,Set,Map,Symbol,循环引用都考虑在内

然鹅,还有 Buffer,Promise,proxy 等…

lodash

真香

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 = _.clone(oldObj)
// 函数
console.log(newObj.a, oldObj.a); // [Function: say] [Function: say]
// 稀疏数组
console.log(newObj.b[0], oldObj.b[0]); // undefined undefined
// 正则对象
console.log(newObj.c, oldObj.c); // /ab+c/i /ab+c/i
// 构造函数
console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: person] [Function: person]
//循环引用
var oldObj = {};;
oldObj.a = oldObj;
const newObj = _.clone(oldObj)
console.log(oldObj,newObj)//{a: {…}} {a: {…}}