js 深克隆(考虑到类型检查,递归爆栈,相同引用,Date和Function等特殊类型克隆,原型克隆)

网上看到很多js深克隆的文章,写法各有千秋,但大多考虑不够全面; 并且很多一测试就报错,或者返回结果不对。在下不才,整理了下写了个比较完善的方法,全篇带注释讲解,并附测试用例。欢迎各路牛鬼蛇神深入探讨。

深克隆需要注意的问题

  1. 入参类型检查
  2. 当数据量较大并层次很深时,使用递归函数会导致栈溢出,而此处又无法使用尾递归,该怎么处理
  3. typeof Date,Math,RegExp,Function,Null 都返回Object 该怎么处理
  4. Date,RegExp,Function 应该如何克隆
  5. 当对象的两个属性v,s引用同一个对象时,克隆之后也应该引用同一个对象
  6. 对象的原型prototype 如何克隆
  7. 属性的getOwnPropertyDescriptor如何克隆
  8. for-in遍历的是原型链,需要用hasOwnProperty 判断是否是自有属性

定义函数获取数据类型

function _getDataType(data) {
  return Object.prototype.toString.call(data).slice(8, -1);
}

定义函数克隆RegExp类型

function copyRegExp(regExp) {
  let attrs = '';
  if (regExp.global) attrs += 'g';
  if (regExp.ignoreCase) attrs += 'i';
  if (regExp.multiline) attrs += 'm';
  let newRegExp = new RegExp(regExp, attrs);
  newRegExp.lastIndex = regExp.lastIndex;
  return newRegExp;
}

定义克隆函数

function clone(x) {
  // String Number Boolean Undefined Null 返回自身
  if (x == null || typeof x !== 'object') return x;
  // RegExp Date Function 克隆
  let type = _getDataType(x);
  let root;
  switch (type) {
    case 'RegExp':
      return copyRegExp(x);
    case 'Date':
      return new Date(x.getTime());
    case 'Function':
      return x;
    case 'Array':
      root = [];
      break;
    default:
      root = Object.create(Object.getPrototypeOf(x));
  }
  // Array Object 克隆
  // 用来去重 解决原数据中多个属性引用同一对象克隆后不相同问题
  const uniqueList = [];
  // 使用栈结构解决递归爆栈问题
  const stack = [
    {
      parent: root,
      key: undefined,
      data: x,
    }
  ];
  // 深度优先循环
  while (stack.length) {
    const {parent, key, data} = stack.pop();
    // 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
    let res = parent;
    if (typeof key !== 'undefined') {
      let type = _getDataType(data);
      switch (type) {
        case 'RegExp':
          parent[key] = copyRegExp(data);
          continue;
        case 'Date':
          parent[key] = new Date(data.getTime());
          continue;
        case 'Function':
          parent[key] = data;
          continue;
        case 'Array':
          res = parent[key] = [];
          break;
        default:
          let proto = Object.getPrototypeOf(data);
          res = parent[key] = Object.create(proto);
      }
    }
    //数据引用已经存在则赋值并退出本次循环,不存在则缓存
    let uniqueData = uniqueList.find(item => item.source === data);
    if (uniqueData) {
      parent[key] = uniqueData.target;
      continue;
    } else {
      uniqueList.push({
        source: data,
        target: res,
      });
    }
    for (let k in data) {
      if (data.hasOwnProperty(k)) {
        if (data[k] == null || typeof data[k] !== 'object') {
          // 基础类型克隆
          let descriptor=Object.getOwnPropertyDescriptor(data,k);
          Object.defineProperty(res,k,descriptor);
        } else {
          // 引用类型加入stack循环处理
          stack.push({
            parent: res,
            key: k,
            data: data[k],
          });
        }
      }
    }
  }
  return root;
}

测试用例

let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
let obj = {
  a: new Date(),
  b: undefined,
  c: /234/igm,
  d: {},
  h: true,
  i: 234,
  k: 'sdfsd',
  m: [1, 2, 3],
  n: {f: 3, g: [3, new Date(), /ewrew/ig, 45, 8, {d: 33}]},
  o: /sdf/i,
  w: function () {
    console.log(44)
  },
  r: null,
  v: arr,
  s: arr,
};
let cloneObj=clone(obj);
arr[2]=3333;
cloneObj.s[3]=5555;
console.log(obj);
console.log(cloneObj);

测试结果

{ a: 2019-01-22T12:54:24.955Z,
  b: undefined,
  c: /234/gim,
  d: {},
  h: true,
  i: 234,
  k: 'sdfsd',
  m: [ 1, 2, 3 ],
  n:
   { f: 3,
     g: [ 3, 2019-01-22T12:54:24.955Z, /ewrew/gi, 45, 8, [Object] ] },
  o: /sdf/i,
  w: [Function: w],
  r: null,
  v: [ 1, 2, 3333, 4, 5, 6, 7, 8, 9 ],
  s: [ 1, 2, 3333, 4, 5, 6, 7, 8, 9 ] }
{ b: undefined,
  h: true,
  i: 234,
  k: 'sdfsd',
  w: [Function: w],
  r: null,
  s: [ 1, 2, 3, 5555, 5, 6, 7, 8, 9 ],
  v: [ 1, 2, 3, 5555, 5, 6, 7, 8, 9 ],
  o: /sdf/i,
  n:
   { f: 3,
     g: [ 3, 2019-01-22T12:54:24.955Z, /ewrew/gi, 45, 8, [Object] ] },
  m: [ 1, 2, 3 ],
  d: {},
  c: /234/gim,
  a: 2019-01-22T12:54:24.955Z }

简单深克隆方法

let obj={};
let cloneObj=JSON.parse(JSON.stringify(obj));

浅克隆 Object.assign(target, …sources)

let o0 = [1, 2, 3, 4];
let o1 = {a: 1, b: 1, c: 1, d: o0};
let o2 = {b: 2, c: 2};
let obj = Object.assign({}, o1, o2);
o1.d[1]=444;
console.log(obj); // { a: 1, b: 2, c: 2, d: [ 1, 444, 3, 4 ] }

浅克隆 自己实现

Object.prototype.clone=function(){
  //原型指向保持一致
  var newobj=Object.create(Object.getPrototypeOf(this));
  //自身属性保持一样
  var propNames=Object.getOwnPropertyNames(this);
  propNames.forEach(function(item){
    //保持每个属性的特性也一样
    var des=Object.getOwnPropertyDescriptor(this,item);
    Object.defineProperty(newobj,item,des);
  },this);
  return newobj;
}
//测试
let o0 = [1, 2, 3, 4];
let o1 = {a: 1, b: 2, c: 1, d: o0};
let obj = o1.clone();
o1.d[1]=444;
console.log(obj); // { a: 1, b: 2, c: 1, d: [ 1, 444, 3, 4 ] }

(ps:如有考虑不周或错误之处,请指正。如果有用,请点赞o( ̄▽ ̄)d)

  • 16
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值