手写深拷贝

手写深拷贝

深拷贝

深拷贝简单理解就是b是a的一份拷贝,且b中不存在a中对象的引用

深拷贝的实现

1.JSON序列化和反序列化

如果对象中全是基本类型,那么可以使用JSON.parse(JSON.stringify(a))

2.递归克隆

2.1 如果是普通类型,就直接拷贝

测试用例:

describe('deepClone', () => {
  it('能够复制基本类型number,string,boolean,undefined,null,symbol', () => {
    const n = 123;
    const n2 = deepClone(n);
    assert(n === n2);
    const s = '12345';
    const s2 = deepClone(s);
    assert(s === s2);
    const b = true;
    const b2 = deepClone(b);
    assert(b === b2);
    const u = undefined;
    const u2 = deepClone(u);
    assert(u === u2);
    const empty = null;
    const empty2 = deepClone(empty);
    assert(empty === empty2);
    const sym = Symbol();
    const sym2 = deepClone(sym);
    assert(sym === sym2);
  });
});

代码实现:

function deepClone(source) {
  return source;
}
image.png
image.png

此时我们能够实现基本类型的拷贝

2.2 如果是对象

2.2.1 普通对象

使用for in 遍历对象上的属性,返回一个新的对象(注: for in 会遍历原型上的属性)
测试用例:

it('能够复制对象', () => {
      const obj1 = { name: 'sss', child: { name: 'sss-children' } };
      const obj2 = deepClone(obj1);
      assert(obj1 !== obj2);
      assert(obj1.name === obj2.name);
      assert(obj1.child !== obj2.child);
      assert(obj1.child.name === obj2.child.name);
    });

代码实现:

function deepClone(source) {
  if (source instanceof Object) {
    let dist = new Object();
    for (let key in source) {
      // for in会遍历原型上的属性
      if (source.hasOwnProperty(key)) {
        dist[key] = deepClone(source[key]);
      }
    }

    return dist;
  }

  return source;
}
image.png
image.png
2.2.2 数组对象

使用new Array初始化

测试用例:

it('能够复制数组对象', () => {
      const array1 = [
        [11, 12],
        [21, 22],
        [31, 32],
      ];
      const array2 = deepClone(array1);
      assert(array1 !== array2);
      assert(array1[0] !== array2[0]);
      assert(array1[1] !== array2[1]);
      assert(array1[2] !== array2[2]);
      assert.deepEqual(array1, array2);
    });

代码实现:

function deepClone(source) {
  if (source instanceof Object) {
    let dist;
    if (source instanceof Array) {
      dist = new Array();
    } else {
      dist = new Object();
    }

    for (let key in source) {
      // for in会遍历原型上的属性
      if (source.hasOwnProperty(key)) {
        dist[key] = deepClone(source[key]);
      }
    }

    return dist;
  }

  return source;
}
2.2.3 函数对象

返回一个新的函数,使用apply(this,arguments)

测试用例:

 it('能够复制函数', () => {
      const f1 = function (x, y) {
        return x + y;
      };
      f1.xxx = { yyy: { zzz: 1 } };
      const f2 = deepClone(f1);
      assert(f1 !== f2);
      assert(f1.xxx !== f2.xxx);
      assert(f1.xxx.yyy !== f2.xxx.yyy);
      assert(f1.xxx.yyy.zzz === f2.xxx.yyy.zzz);
      assert(f1(1, 2) === f2(1, 2));
    });

代码实现:

function deepClone(source) {
  if (source instanceof Object) {
    let dist;
    if (source instanceof Array) {
      dist = new Array();
    } else if (source instanceof Function) {
      dist = function () {
        return source.apply(this, arguments);
      };
    } else {
      dist = new Object();
    }
    for (let key in source) {
      // for in会遍历原型上的属性
      if (source.hasOwnProperty(key)) {
        dist[key] = deepClone(source[key]);
      }
    }

    return dist;
  }

  return source;
}
2.2.4 日期和正则

日期使用new Date创建一个新的日期对象,正则使用new RegExp(source.source, source.flags)

测试用例:

 it('可以复制正则', () => {
      const reg1 = /hi\d+/gi;
      reg1.xxx = { yyy: { zzz: 1 } };
      const reg2 = deepClone(reg1);
      assert(reg1.source === reg2.source);
      assert(reg1.flags === reg2.flags);
      assert(reg1 !== reg2);
      assert(reg1.xxx !== reg2.xxx);
      assert(reg1.xxx.yyy !== reg2.xxx.yyy);
      assert(reg1.xxx.yyy.zzz === reg2.xxx.yyy.zzz);
    });
    it('可以复制日期', () => {
      const date1 = new Date();
      date1.xxx = { yyy: { zzz: 1 } };
      const date2 = deepClone(date1);
      assert(date1.source === date2.source);
      assert(date1.flags === date2.flags);
      assert(date1 !== date2);
      assert(date1.getTime() === date2.getTime());
      assert(date1.xxx !== date2.xxx);
      assert(date1.xxx.yyy !== date2.xxx.yyy);
      assert(date1.xxx.yyy.zzz === date2.xxx.yyy.zzz);
    });

代码实现

function deepClone(source) {
  if (source instanceof Object) {
    let dist;
    if (source instanceof Array) {
      dist = new Array();
    } else if (source instanceof Function) {
      dist = function () {
        return source.apply(this, arguments);
      };
    } else if (source instanceof RegExp) {
      dist = new RegExp(source.source, source.flags);
    } else if (source instanceof Date) {
      dist = new Date(source);
    } else {
      dist = new Object();
    }
    for (let key in source) {
      // for in会遍历原型上的属性
      if (source.hasOwnProperty(key)) {
        dist[key] = deepClone(source[key]);
      }
    }

    return dist;
  }

  return source;
}
image.png
image.png

目前我们粗略的实现了一个可以复制基本类型和常见对象的深拷贝

2.3 考虑环

const a = {}
a.self = a

测试用例:

it('环也能复制', () => {
      const obj1 = { name: 'sss' };
      obj1.self = obj1;
      const obj2 = deepClone(obj1);
      assert(obj1 !== obj2);
      assert(obj1.name === obj2.name);
      assert(obj1.self !== obj2.self);
    });

此时我们直接运行之前的代码,发现控制台直接报错了。因为对象每次深拷贝之后会生成一个全新的对象,所以此时会造成递归不能终止。因此我们需要先判断是否存在环,然后进行对象的拷贝。

我们创建一个cashe数组,里面存放origin和new

let cache = [];

function findCache(source) {
  for (let i = 0; i < cache.length; i++) {
    if (cache[i].origin && cache[i].origin === source) {
      return cache[i].new;
    }
  }
  return undefined;
}
function deepClone(source) {
  if (source instanceof Object) {
    let cachedDist = findCache(source);
    if (cachedDist) {
      return cachedDist;
    } else {
      let dist;
      if (source instanceof Array) {
        dist = new Array();
      } else if (source instanceof RegExp) {
        dist = new RegExp(source.source, source.flags);
      } else if (source instanceof Function) {
        dist = function () {
          return source.apply(this, arguments);
        };
      } else if (source instanceof Date) {
        dist = new Date(source);
      } else {
        dist = new Object();
      }
      cache.push({ origin: source, new: dist });
      for (let key in source) {
        // for in会遍历原型上的属性
        if (source.hasOwnProperty(key)) {
          dist[key] = deepClone(source[key]);
        }
      }

      return dist;
    }
  }
  return source;
}
image.png
image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容