Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第 95 题:模拟实现一个深拷贝,并考虑对象相互引用以及 Symbol 拷贝的情况 #148

Open
yygmind opened this issue Jun 28, 2019 · 47 comments

Comments

@yygmind
Copy link
Contributor

yygmind commented Jun 28, 2019

No description provided.

@kungithub
Copy link

function deepClone(obj, hash = new WeakMap()) {
    if (hash.has(obj)) return obj;
    var cobj;
    // null
    if (obj === null) { return obj }
    let t = typeof obj;

    // 基本类型
    switch (t) {
        case 'string':
        case 'number':
        case 'boolean':
        case 'undefined':
            return obj;
    }

    // 数组
    if (Array.isArray(obj)) {
        cobj = [];
        obj.forEach((c, i) => { cobj.push(deepClone(obj[i])) });
    } else {
        cobj = {};
        // object // symbol
        if (Object.prototype.toString.call(obj) === "[object Object]") {
            Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj)).forEach(c => {
                hash.set(obj, obj);
                cobj[c] = deepClone(obj[c], hash);
            });
        } else {
            //内置Object
            cobj = obj;
        }
    }
    return cobj;
}

image

不知道有没有漏的。。

@lhyt
Copy link

lhyt commented Jun 28, 2019

一个不考虑其他数据类型的公共方法,基本满足大部分场景

function deepCopy(target, cache = new Set()) {
  if (typeof target !== 'object' || cache.has(target)) {
    return target
  }
  if (Array.isArray(target)) {
    target.map(t => {
      cache.add(t)
      return t
    })
  } else {
    return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((res, key) => {
      cache.add(target[key])
      res[key] = deepCopy(target[key], cache)
      return res
    }, target.constructor !== Object ? Object.create(target.constructor.prototype) : {})
  }
}

主要问题是

  1. symbol作为key,不会被遍历到,所以stringify和parse是不行的
  2. 有环引用,stringify和parse也会报错

我们另外用getOwnPropertySymbols可以获取symbol key可以解决问题1,用集合记忆曾经遍历过的对象可以解决问题2。当然,还有很多数据类型要独立去拷贝。比如拷贝一个RegExp,lodash是最全的数据类型拷贝了,有空可以研究一下

另外,如果不考虑用symbol做key,还有两种黑科技深拷贝,可以解决环引用的问题,比stringify和parse优雅强一些

function deepCopyByHistory(target) {
  const prev = history.state
  history.replaceState(target, document.title)
  const res = history.state
  history.replaceState(prev, document.title)
  return res
}

async function deepCopyByMessageChannel(target) {
  return new Promise(resolve => {
    const channel = new MessageChannel()
    channel.port2.onmessage = ev => resolve(ev.data)
    channel.port1.postMessage(target)
  }).then(data => data)
}

无论哪种方法,它们都有一个共性:失去了继承关系,所以剩下的需要我们手动补上去了,故有Object.create(target.constructor.prototype)的操作

@dongj0316
Copy link

  const symbolName = Symbol();
  const obj = {
    objNumber: new Number(1),
    number: 1,
    objString: new String('ss'),
    string: 'stirng',
    objRegexp: new RegExp('\\w'),
    regexp: /w+/g,
    date: new Date(),
    function: function () {},
    array: [{a: 1}, 2],
    [symbolName]: 111
  }
  obj.d = obj;

  const isObject = obj => obj !== null && (typeof obj === 'object' || typeof obj === 'function');
  const isFunction = obj => typeof obj === 'function'
  function deepClone (obj, hash = new WeakMap()) {
    if (hash.get(obj)) {
      // 环处理
      return hash.get(obj);
    }
    if (!isObject(obj)) {
      return obj;
    }

    if (isFunction(obj)) {
      // function返回原引用
      return obj;
    }

    let cloneObj;

    const Constructor = obj.constructor;

    switch (Constructor) {
      case Boolean:
      case Date:
        return new Date(+obj);
      case Number:
      case String:
      case RegExp:
        return new Constructor(obj);
      default:
        cloneObj = new Constructor();
        hash.set(obj, cloneObj);
    }

    [...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj)].forEach(k => {
      cloneObj[k] = deepClone(obj[k], hash);
    })
    return cloneObj;
  }
  

  const o = deepClone(obj)
  console.log(o.objNumber === obj.objNumber);
  console.log(o.number === obj.number);
  console.log(o.objString === obj.objString);
  console.log(o.string === obj.string);
  console.log(o.objRegexp === obj.objRegexp);
  console.log(o.regexp === obj.regexp);
  console.log(o.date === obj.date);
  console.log(o.function === obj.function);
  console.log(o.array[0] === obj.array[0]);
  console.log(o[symbolName] === obj[symbolName]);

@lovelope
Copy link

lovelope commented Jul 2, 2019

一个不考虑其他数据类型的公共方法,基本满足大部分场景

function deepCopy(target, cache = new Set()) {
  if (typeof target !== 'object' || cache.has(target)) {
    return target
  }
  if (Array.isArray(target)) {
    target.map(t => {
      cache.add(t)
      return t
    })
  } else {
    return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((res, key) => {
      cache.add(target[key])
      res[key] = deepCopy(target[key], cache)
      return res
    }, target.constructor !== Object ? Object.create(target.constructor.prototype) : {})
  }
}

主要问题是

  1. symbol作为key,不会被遍历到,所以stringify和parse是不行的
  2. 有环引用,stringify和parse也会报错

我们另外用getOwnPropertySymbols可以获取symbol key可以解决问题1,用集合记忆曾经遍历过的对象可以解决问题2。当然,还有很多数据类型要独立去拷贝。比如拷贝一个RegExp,lodash是最全的数据类型拷贝了,有空可以研究一下

另外,如果不考虑用symbol做key,还有两种黑科技深拷贝,可以解决环引用的问题,比stringify和parse优雅强一些

function deepCopyByHistory(target) {
  const prev = history.state
  history.replaceState(target, document.title)
  const res = history.state
  history.replaceState(prev, document.title)
  return res
}

async function deepCopyByMessageChannel(target) {
  return new Promise(resolve => {
    const channel = new MessageChannel()
    channel.port2.onmessage = ev => resolve(ev.data)
    channel.port1.postMessage(target)
  }).then(data => data)
}

无论哪种方法,它们都有一个共性:失去了继承关系,所以剩下的需要我们手动补上去了,故有Object.create(target.constructor.prototype)的操作

有两个问题:

  1. 如果 target 是一个数组,拷贝结果没有返回
  2. 如果 target 是一个函数,函数没有被深拷贝

@lhyt
Copy link

lhyt commented Jul 2, 2019

一个不考虑其他数据类型的公共方法,基本满足大部分场景

function deepCopy(target, cache = new Set()) {
  if (typeof target !== 'object' || cache.has(target)) {
    return target
  }
  if (Array.isArray(target)) {
    target.map(t => {
      cache.add(t)
      return t
    })
  } else {
    return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((res, key) => {
      cache.add(target[key])
      res[key] = deepCopy(target[key], cache)
      return res
    }, target.constructor !== Object ? Object.create(target.constructor.prototype) : {})
  }
}

主要问题是

  1. symbol作为key,不会被遍历到,所以stringify和parse是不行的
  2. 有环引用,stringify和parse也会报错

我们另外用getOwnPropertySymbols可以获取symbol key可以解决问题1,用集合记忆曾经遍历过的对象可以解决问题2。当然,还有很多数据类型要独立去拷贝。比如拷贝一个RegExp,lodash是最全的数据类型拷贝了,有空可以研究一下
另外,如果不考虑用symbol做key,还有两种黑科技深拷贝,可以解决环引用的问题,比stringify和parse优雅强一些

function deepCopyByHistory(target) {
  const prev = history.state
  history.replaceState(target, document.title)
  const res = history.state
  history.replaceState(prev, document.title)
  return res
}

async function deepCopyByMessageChannel(target) {
  return new Promise(resolve => {
    const channel = new MessageChannel()
    channel.port2.onmessage = ev => resolve(ev.data)
    channel.port1.postMessage(target)
  }).then(data => data)
}

无论哪种方法,它们都有一个共性:失去了继承关系,所以剩下的需要我们手动补上去了,故有Object.create(target.constructor.prototype)的操作

有两个问题:

  1. 如果 target 是一个数组,拷贝结果没有返回
  2. 如果 target 是一个函数,函数没有被深拷贝

数组的确是我忘了写return了。然后拷贝函数这种操作平时真不会有人做。如果实在是要拷贝,除了简单的function.toString和正则匹配外,还要考虑箭头函数、参数默认值、换行、this、函数名字

@Zousdie
Copy link

Zousdie commented Jul 7, 2019

  1. 如果obj是null, 或者不是函数也不是object(即为包括Symbol在内的基本类型)则直接返回obj;
  2. 如果obj是Date或RegExp就返回对应的新实例;
  3. 在map中查找,找到则返回;
  4. 以上都不是,则通过new obj.constructor()eval(obj.toString())创建一个新实例temp,并保存进map,通过Object.getOwnPropertyNamesObject.getOwnPropertySymbols遍历obj的所有属性名,递归调用deepClone完成temp上所有属性的声明和赋值,最后返回temp
function deepClone(obj, map = new WeakMap()) {
  const type = typeof obj;

  if (obj === null || type !== 'function' && type !== 'object') return obj;
  if (obj instanceof Date) return Date(obj);
  if (obj instanceof RegExp) return RegExp(obj);
  if (map.has(obj)) return map.get(obj);

  const temp = type === 'function' ? eval(obj.toString()) : new obj.constructor();
  map.set(obj, temp);

  Object.getOwnPropertyNames(obj)
    .concat(Object.getOwnPropertySymbols(obj))
    .forEach((i) => {
      temp[i] = deepClone(obj[i], map);
    });

  return temp;
}

函数拷贝的情况太复杂了,所以就直接用了eval(obj.toString())

@yeyan1996
Copy link

写个不用递归用循环的方式实现的版本吧

const getType = obj => Object.prototype.toString.call(obj).match(/\[object\s(.*)]/)[1]

function deepClone(obj) {
    let res = {}
    let stack = []
    let root = {
        parent: obj,
        prop: null,
        data: res
    }
    let wm = new WeakMap()
    stack.push(root)

    while (stack.length) {
        let item = stack.pop()
        Reflect.ownKeys(item.parent).forEach(key => {
            if (wm.get(item.parent[key])) {
                item.data[key] = wm.get(item.parent[key])
                return
            }
            switch (getType(item.parent[key])) {
                case 'Object': {
                    item.data[key] = {}
                    stack.push({
                        parent: item.parent[key],
                        prop: key,
                        data: item.data[key]
                    })
                    wm.set(item.parent[key], item.parent[key])
                    break
                }
                case 'Array': {
                    item.data[key] = []
                    stack.push({
                        parent: item.parent[key],
                        prop: key,
                        data: item.data[key]
                    })
                    wm.set(item.parent[key], item.parent[key])
                    break
                }
                case 'Date': {
                    item.data[key] = new Date(item.parent[key])
                    break
                }
                case 'RegExp': {
                    item.data[key] = new RegExp(item.parent[key])
                    break
                }
                default: {
                    item.data[key] = item.parent[key]
                }
            }
        })
    }

    return res
}


let obj = {
    num: 0,
    str: '',
    boolean: true,
    unf: undefined,
    nul: null,
    obj: {
        name: '我是一个对象',
        id: 1,
        qwe: {
            a: 1
        }
    },
    arr: [0, 1, 2, {b: 2}],
    date: new Date(0),
    reg: /我是一个正则/ig,
    [Symbol('1')]: 1,
    func() {
        console.log(123)
    }
};

obj.loop = obj

let cloneObj = deepClone(obj);

console.log('obj', obj);
console.log('cloneObj', cloneObj);

// 对比两个对象引用类型的值是相同
Object.keys(cloneObj).filter(key => key !== 'nul').forEach(key => {
    if (typeof cloneObj[key] === 'object' || typeof cloneObj[key] === 'function') {
        console.log(`${key}相同吗? `, cloneObj[key] === obj[key])
    }
})

函数拷贝不了,还有一些奇奇怪怪的引用类型也拷贝不了,一般情况应该没啥问题,其实拷贝函数有一种思路是用AST(手动狗头)

@komaedaXnagito
Copy link

function deepClone(obj, hash = new WeakMap()) {
    if (hash.has(obj)) return obj;
    var cobj;
    // null
    if (obj === null) { return obj }
    let t = typeof obj;

    // 基本类型
    switch (t) {
        case 'string':
        case 'number':
        case 'boolean':
        case 'undefined':
            return obj;
    }

    // 数组
    if (Array.isArray(obj)) {
        cobj = [];
        obj.forEach((c, i) => { cobj.push(deepClone(obj[i])) });
    } else {
        cobj = {};
        // object // symbol
        if (Object.prototype.toString.call(obj) === "[object Object]") {
            Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj)).forEach(c => {
                hash.set(obj, obj);
                cobj[c] = deepClone(obj[c], hash);
            });
        } else {
            //内置Object
            cobj = obj;
        }
    }
    return cobj;
}

var a=[];
a.push(a);
deepClone(a);

@richard1015
Copy link

野路子

缺点:无法拷贝函数 、Symbol

const deepClone = function (obj) {
    var str = JSON.stringify(obj);
    return JSON.parse(str);
}

@NathanHan1
Copy link

Symbol 不是独一无二的吗?还能拷贝?

@NathanHan1
Copy link

函数为什么要拷贝?函数不是用来复用的吗?

@Gemiry
Copy link

Gemiry commented Jul 25, 2019

image
@lhyt

@jiao2563719877
Copy link

function clone(param) {
    // 数组 、 对象 、 普通值
    let res = null
    let type = Object.prototype.toString.call(param)
    if (type === '[object Object]') {
      res = {}
      for (const key in param) {
        res[key] = clone(param[key])
      }
    } else if (type === '[object Array]') {
      res = []
      param.forEach((item, index) => {
        res[index] = clone(item)
      })
    } else {
      res = param
    }
    return res
  }

  let o = { fn: () => { }, name: 1, o: { a: [1], name: 2, fn: () => { } }, arr: [1, 2, 3], s: Symbol() }
  let o2 = clone(o)
  o2.name = 200
  console.log(o , o2)

@JackFGreen
Copy link

分享一个来自 Vuex 的 deepCopy
解决了循环引用,cache 存储所有嵌套 obj 及其 copy 副本,以 cache 中是否有某个嵌套 obj 来判断是否循环引用,有则返回 引用的 copy

export function deepCopy (obj, cache = []) {
  // just return if obj is immutable value
  if (obj === null || typeof obj !== 'object') {
    return obj
  }

  // if obj is hit, it is in circular structure
  const hit = find(cache, c => c.original === obj)
  if (hit) {
    return hit.copy
  }

  const copy = Array.isArray(obj) ? [] : {}
  // put the copy into cache at first
  // because we want to refer it in recursive deepCopy
  cache.push({
    original: obj,
    copy
  })

  Object.keys(obj).forEach(key => {
    copy[key] = deepCopy(obj[key], cache)
  })

  return copy
}

@zzNire
Copy link

zzNire commented Aug 14, 2019

为什么没人用getOwnPropertyDescriptors

@yft
Copy link

yft commented Aug 20, 2019

@lhyt

function deepCopy(target, cache = new Set()) {
  if (typeof target !== 'object' || cache.has(target)) {
    return target
  }
  if (Array.isArray(target)) {
    target.map(t => {
      cache.add(t)
      return t
    })
  } else {
    return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((res, key) => {
      cache.add(target[key])
      res[key] = deepCopy(target[key], cache)
      return res
    }, target.constructor !== Object ? Object.create(target.constructor.prototype) : {})
  }
}

在这种情况下有问题

let ooo = {
    a: {}
};

ooo.a.c = ooo;

let eee = deepCopy(ooo)
console.log(eee.a.c === eee);
console.log(eee.a.c === ooo);

感觉环状数据之前这个里面 #10 处理的方法可以

@yaodongyi
Copy link

yaodongyi commented Sep 20, 2019

看大家都写的好复杂,我来一个简单点的吧。

const pub = new (class {
  // static obj = {};
  constructor() {}
  deepCopy(obj) {
    let result = Array.isArray(obj) ? [] : {};
    // 获取到当前层对象的所有属性。
    let ownProperty = [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)];
    for (let i in ownProperty) {
      if (obj.hasOwnProperty(ownProperty[i])) {
        // console.log(ownProperty[i], ':', obj[ownProperty[i]]);
        if (typeof obj[ownProperty[i]] === 'object' && obj[ownProperty[i]] != null) {
          result[ownProperty[i]] = this.deepCopy(obj[ownProperty[i]]);
        } else {
          result[ownProperty[i]] = obj[ownProperty[i]];
        }
      }
    }
    return result;
  }
})();

const HEAD = Symbol('头顶');
const HAT = Symbol('帽子');
const CAUSE = Symbol('原因');
// 新建一个魔性的对象。
let obj = {
  origin: { name: '小明', [HEAD]: '🍃' },
  [HAT]: { [CAUSE]: 'wife', color: 'green', background: '🌾', num: [1, 2, 3, 4, 5, 6, 7, 8, 9, [Infinity]] },
  move: function() {}
};

// 接下来对这个魔性的对象进行深拷贝,太残忍了。
let objCopy = pub.deepCopy(obj);

// 验证
obj[HAT].num[1] = 0;
console.log(obj, objCopy);
console.log('obj:', obj[HAT].num[1], ', objCopy:', objCopy[HAT].num[1]); // obj: 0 , objCopy: 2

@307590317
Copy link

307590317 commented Oct 22, 2019

不考虑正则、函数等奇怪类型的拷贝,满足大多数深度拷贝需求
定制需求如下:
1、循环引用
2、Symbol 类型拷贝

function deepClone(val,map = new WeakMap()){
    if(val === null || typeof val !=='object') return val;
    //循环引用
    if(map.has(val)) return map.get(val);
    let clone = Array.isArray(val) ? [] : {};
    map.set(val,clone);
    // 获取对象中所有的属性名(包含Symbol值)
    let keys = Reflect.ownKeys(val);(可换为:Object.keys(val).concat(Object.ownPropertySymbols(val))
    let len = keys.length;
    while(len--){
        clone[keys[len]] = deepClone(val[keys[len]],map);
    }
    return clone;
}

欢迎交流

@maginapp
Copy link

const deepCopy = (data, map = new Map()) => {
  let result
  if (map.has(data)) {
    return map.get(data)
  } else if (data instanceof Array) {
    result = []
    map.set(data, result)
    data.forEach(item => result.push(deepCopy(item, map)))
  } else if (typeof data === 'object') {
    result = {}
    map.set(data, result)
    for (let k of Reflect.ownKeys(data)) {
      result[k] = deepCopy(data[k], map)
    }
  } else {
    result = data
  }
  return result
}

@SSSSSFFFFF
Copy link

记录一下简单做的

let objc = {
    a: 1,
    b: Symbol('2'),
    c:{
        d: Symbol('3'),
        e: {
            f: Symbol('4'),
        }
    }
};

function deepClone(obj) { 
    let result = {}
    for (const key in obj) {
        typeof (obj[key]) == 'object' ? 
            result[key] = deepClone(obj[key]) : 
            result[key] = obj[key]
    }
    return result
}
let objct = deepClone(objc)
objct.c.e.f = 2
console.log(objc);
console.log(objct);

@litokele2018
Copy link

function type(data) {
  let type = Object.prototype.toString.call(data)
  if (type === '[object Array]') {
    return []
  } else if (type === '[object Object]') {
    return {}
  } else {
    return data
  }
}

function deepClone(data) {
  let map = new Map() // 处理环状
  let deepCloneFunc = function (data) {
    let result = type(data)
    if (map.get(data)) {  //处理环状
      result = data
      return result
    } 
    if (result !== data) { // 不是基本数据类型 
      map.set(data, result) // 为了判断该对象是否出现过,处理环状
      const objectSymbolsKey = Object.getOwnPropertySymbols(data) // 普通遍历key是获取不到key 为Symbol的
      if (objectSymbolsKey.length) {
        for (let i in objectSymbolsKey) {
          result[objectSymbolsKey[i]] = deepCloneFunc(data[objectSymbolsKey[i]]) 
        }
      }
      for (let key in data) {
        result[key] = deepCloneFunc(data[key])
      }
      return result
    } else {
      return data
    }
  }
  return deepCloneFunc(data)
}
let objx ={}
objx.repeat = objx

let obj = {
  [Symbol('name')]: 'litokele',
  gender: Symbol('male'),
  age: 18,
  favoriteAnime: ['xxx1', 'xxx2'],
  obj: {
    [Symbol('test')]: 'test',
    name: 'kele',
    age: 18
  },
  repeat: objx
}
let myObj = deepClone(obj)

console.log("my_ojb:", myObj)

QQ截图20200330152443

@nbili
Copy link

nbili commented Mar 31, 2020

var deepClone = (target, hash = new WeakMap) => {
    if (target == null) return target
    if (typeof target !== 'object') return target
    if (target instanceof RegExp) return new RegExp(target)
    if (target instanceof Date) return new Date(target)
    if (hash.has(target)) return hash.get(target)

    var instance = new target.constructor
    hash.set(target, instance)
    
    Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target)).forEach(key => {
        instance[key] = deepClone(target[key], hash)
    })

    return instance
}

@1368725603
Copy link

function cloneObj(obj) {
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' ? cloneObj(val) : val;
}
}
return newObj;
};

@coveyz
Copy link

coveyz commented May 6, 2020

流下了没有技术的眼泪...

@korylee
Copy link

korylee commented May 6, 2020

少了个find函数

const find = (cache, fn) => cache.filter(fn)[0]

分享一个来自 Vuex 的 deepCopy
解决了循环引用,cache 存储所有嵌套 obj 及其 copy 副本,以 cache 中是否有某个嵌套 obj 来判断是否循环引用,有则返回 引用的 copy

export function deepCopy (obj, cache = []) {
  // just return if obj is immutable value
  if (obj === null || typeof obj !== 'object') {
    return obj
  }

  // if obj is hit, it is in circular structure
  const hit = find(cache, c => c.original === obj)
  if (hit) {
    return hit.copy
  }

  const copy = Array.isArray(obj) ? [] : {}
  // put the copy into cache at first
  // because we want to refer it in recursive deepCopy
  cache.push({
    original: obj,
    copy
  })

  Object.keys(obj).forEach(key => {
    copy[key] = deepCopy(obj[key], cache)
  })

  return copy
}

@shadow-Fiend
Copy link

// 咱来个简单易懂的
function deepClone(obj) {
	let newObj = Array.isArray(obj) ? [...obj] : {...obj}
	Reflect.ownKeys(obj).forEach(o => {
		if(obj[o] && typeof obj[o] === 'object') {
			newObj[o] = deepClone(obj[o])
		} else {
			newObj[o] = obj[o]
		}
	})
	return newObj
}

@yuanxiang1990
Copy link

参考上面几位大佬的答案整理了一下

function deepClone(target, cache = []) {
  if (typeof target !== 'object') {
    return target
  }

  let hit = cache.filter(item => item.origin === target)[0];
  if (hit) {
    return hit.copy;
  }

  if (Array.isArray(target)) {
    return target.map(item => {
      return deepClone(item, cache);
    })
  }
  let copy = target.constructor === Object ? {} : Object.create(target.constructor.prototype)
  cache.push({
    origin: target,
    copy
  })
  return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((clone, key) => {
    clone[key] = deepClone(target[key], cache);
    return clone;
  }, copy)
}

@Bigzo
Copy link

Bigzo commented Sep 7, 2020

function deepClone(obj, hash = new WeakMap()) {
    if (hash.has(obj)) return obj;
    var cobj;
    // null
    if (obj === null) { return obj }
    let t = typeof obj;

    // 基本类型
    switch (t) {
        case 'string':
        case 'number':
        case 'boolean':
        case 'undefined':
            return obj;
    }

    // 数组
    if (Array.isArray(obj)) {
        cobj = [];
        obj.forEach((c, i) => { cobj.push(deepClone(obj[i])) });
    } else {
        cobj = {};
        // object // symbol
        if (Object.prototype.toString.call(obj) === "[object Object]") {
            Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj)).forEach(c => {
                hash.set(obj, obj);
                cobj[c] = deepClone(obj[c], hash);
            });
        } else {
            //内置Object
            cobj = obj;
        }
    }
    return cobj;
}

image

不知道有没有漏的。。
你处理循环引用错了。

@muzichen
Copy link

muzichen commented Sep 8, 2020

function nativeDeepCopy (origin, hash = new WeakMap()) {
    const type = typeof origin
    let clone
    if (origin === null || type !== 'function' && type !== 'object') return origin
    if (hash.has(origin)) return hash.get(origin)
    try {
        clone = new origin.constructor()
    } catch(e) {
        clone = Object.create(Object.getPrototypeOf(origin))
    }
    hash.set(origin, clone)
    let keys = [...Object.getOwnPropertyNames(origin), ...Object.getOwnPropertySymbols(origin)]
    for (let i = 0; i < keys.length; i++) {
        let descriptor = Object.getOwnPropertyDescriptor(origin, keys[i])
        descriptor.value = nativeDeepCopy(descriptor.value, hash)
        Object.defineProperty(clone, keys[i], descriptor)
    }
    return clone
}

@m7yue
Copy link

m7yue commented Sep 10, 2020

记录一下

const init = (origin) => {
  let type = Object.prototype.toString.call(origin)
  if (type === '[object Array]') {
    return []
  } else if (type === '[object Object]') {
    return {}
  }
}

const deepCopy = (origin, map = new WeakMap()) => {
  const type = typeof origin

  if(!type || (type !== 'function' && type !== 'object')) return origin
  if(map.has(origin)) return map.get(origin) // 相互引用

  if(type == 'function') return origin.bind(null)

  let clone = init(origin)
  map.set(origin, clone)
  
  const opk = Object.getOwnPropertyNames(origin)
  const osk = Object.getOwnPropertySymbols(origin) // Symbol
  const keys = [...opk, ...osk]
  for(let i of keys){
    clone[i] = deepCopy(origin[i], map)
  }

  return clone
}

let circle = {}
circle.repeat = circle

let obj = {
  [Symbol('name')]: '7yue',
  age: 26,
  gender: Symbol('male'),
  favor: ['fav1', 'fav2'],
  say: function(){},
  friends: {
    p1: { [Symbol('name')]: 'p1', age: 24, gender: Symbol('male')},
    p2: { [Symbol('name')]: 'p1', age: 27, gender: Symbol('male')}
  },
  repeat: circle
}

let c = deepCopy(obj)

@chenglu1
Copy link

写个不用递归用循环的方式实现的版本吧

const getType = obj => Object.prototype.toString.call(obj).match(/\[object\s(.*)]/)[1]

function deepClone(obj) {
    let res = {}
    let stack = []
    let root = {
        parent: obj,
        prop: null,
        data: res
    }
    let wm = new WeakMap()
    stack.push(root)

    while (stack.length) {
        let item = stack.pop()
        Reflect.ownKeys(item.parent).forEach(key => {
            if (wm.get(item.parent[key])) {
                item.data[key] = wm.get(item.parent[key])
                return
            }
            switch (getType(item.parent[key])) {
                case 'Object': {
                    item.data[key] = {}
                    stack.push({
                        parent: item.parent[key],
                        prop: key,
                        data: item.data[key]
                    })
                    wm.set(item.parent[key], item.parent[key])
                    break
                }
                case 'Array': {
                    item.data[key] = []
                    stack.push({
                        parent: item.parent[key],
                        prop: key,
                        data: item.data[key]
                    })
                    wm.set(item.parent[key], item.parent[key])
                    break
                }
                case 'Date': {
                    item.data[key] = new Date(item.parent[key])
                    break
                }
                case 'RegExp': {
                    item.data[key] = new RegExp(item.parent[key])
                    break
                }
                default: {
                    item.data[key] = item.parent[key]
                }
            }
        })
    }

    return res
}


let obj = {
    num: 0,
    str: '',
    boolean: true,
    unf: undefined,
    nul: null,
    obj: {
        name: '我是一个对象',
        id: 1,
        qwe: {
            a: 1
        }
    },
    arr: [0, 1, 2, {b: 2}],
    date: new Date(0),
    reg: /我是一个正则/ig,
    [Symbol('1')]: 1,
    func() {
        console.log(123)
    }
};

obj.loop = obj

let cloneObj = deepClone(obj);

console.log('obj', obj);
console.log('cloneObj', cloneObj);

// 对比两个对象引用类型的值是相同
Object.keys(cloneObj).filter(key => key !== 'nul').forEach(key => {
    if (typeof cloneObj[key] === 'object' || typeof cloneObj[key] === 'function') {
        console.log(`${key}相同吗? `, cloneObj[key] === obj[key])
    }
})

函数拷贝不了,还有一些奇奇怪怪的引用类型也拷贝不了,一般情况应该没啥问题,其实拷贝函数有一种思路是用AST(手动狗头)

@Kido-200
Copy link

Kido-200 commented Jan 3, 2021

function deepclone(obj,hash = new WeakMap()){
if(hash.has(obj)) return hash.get(obj)
if(Array.isArray(obj)){
return obj.reduce((res,v)=>{
res.push(deepclone(v,hash))
return res
},[])
}else if(obj instanceof Object){
let nObj = {}
hash.set(obj,nObj)
Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj)).forEach(key => {
nObj[key] = deepclone(obj[key],hash)
})
return nObj
}
return obj
}

@OrangeSAM
Copy link

来源于拉勾JavaScript核心原理精讲课程。

const isComplexDataType = (obj) =>
  (typeof obj === "object" || typeof obj === "function") && obj !== null;

const deepClone = function (obj, hash = new WeakMap()) {
  if (obj.constructor === Date) return new Date(obj); // 日期对象直接返回一个新的日期对象

  if (obj.constructor === RegExp) return new RegExp(obj); //正则对象直接返回一个新的正则对象

  //如果循环引用了就用 weakMap 来解决
  if (hash.has(obj)) return hash.get(obj);

  // getOwnPropertyDescriptors方法返回指定对象上一个自有属性对应的属性描述符
  let allDesc = Object.getOwnPropertyDescriptors(obj);

  //遍历传入参数所有键的特性,Object.create方法创建一个新对象,使用现有的对象来提供新创建对的__proto__
  let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc);

  //继承原型链
  hash.set(obj, cloneObj);

  for (let key of Reflect.ownKeys(obj)) {
    cloneObj[key] =
      isComplexDataType(obj[key]) && typeof obj[key] !== "function"
        ? deepClone(obj[key], hash)
        : obj[key];
  }

  return cloneObj;
};

// 下面是验证代码

let obj = {
  num: 0,

  str: "",

  boolean: true,

  unf: undefined,

  nul: null,

  obj: { name: "我是一个对象", id: 1 },

  arr: [0, 1, 2],

  func: function () {
    console.log("我是一个函数");
  },

  date: new Date(0),

  reg: new RegExp("/我是一个正则/ig"),

  [Symbol("1")]: 1,
};

Object.defineProperty(obj, "innumerable", {
  enumerable: false,
  value: "不可枚举属性",
});

obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj));

obj.loop = obj; // 设置loop成循环引用的属性

let cloneObj = deepClone(obj);

cloneObj.arr.push(4);

console.log("obj", obj);

console.log("cloneObj", cloneObj);

@negativeentropy9
Copy link

function deepClone(src) {
    return recur(src)

    function recur(src) {
        const result = Array.isArray(src) ? [] : {};
        const loopArr = Array.isArray(src) ? Array.from({ length: src.length }, (x, i) => i) : Object.keys(src)
        
        for (let i = 0; i < loopArr.length; i++) {
            switch (typeof src[loopArr[i]]) {
                case 'object':
                    result[loopArr[i]] = recur(src[loopArr[i]]);
                    break;
                default:
                    result[loopArr[i]] = src[loopArr[i]];
            }
        }

        return result;
    }
}

const src = {
    arr: [1, 2, 3],
    obj: { a: 'a' },
    fun() {
        return 'fun'
    },
    num: 1,
    str: 'a',
    boo: false
}

const des = deepClone(src);

src.arr.shift();
src.arr.push(4);
src.obj.a = 'A'

console.log("debug-", src, des);

@Minzax
Copy link

Minzax commented Feb 24, 2021

var a = {
  a: '1',
  b: [1, 2, 3, 4, 5, { t: 'test' }],
  [Symbol()]: 'symbol',
  c: new Date(),
  r: /\d/
}
a.c = { b: a }

function deepClone(obj, hash = new WeakMap()) {
  if(hash.has(obj)) return hash.get(obj);
  if(obj === null || typeof obj !== 'object') return obj;
  let copy;
  if(Array.isArray(obj)) {
    copy = [];
    for(const v of obj) {
      copy.push(deepClone(v));
    }
  } else if(Object.prototype.toString.call(obj) === '[object Object]') {
    copy = {};
    // 考虑循环引用的情况
    hash.set(obj, copy);
    Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj)).forEach((k) => {
      copy[k] = deepClone(obj[k], hash);
    });
  } else {
    return obj;
  }
  return copy;
}

var b = deepClone(a);
console.log(a);
console.log(b);
console.log(a.c.b === b.c.b); // false
console.log(b.c.b === b);     // true

@Saraalwayslikeworld
Copy link

function deepClone(target, cache = new WeakMap()) {   
    // 循环引用
    if(cache.has(target)) {  
        return cache.get(target);
    }
    // 基本类型,函数
    if(typeof target !== 'object') {  
        return target;
    }
    if(target === null) return null;
 
    // 数组
    if(Array.isArray(target)) {
        let newArr = [];
        target.forEach((item,index) => { 
           newArr[index] = deepClone(item, cache)
        });
        return newArr;
    }

    let newObj;
    // 普通对象
    if(target.toString() === '[object Object]') {
        newObj = {};
        cache.set(target, newObj); 
        [...Object.keys(target),...Object.getOwnPropertySymbols(target)].forEach(key => {
            newObj[key] = deepClone(target[key], cache);
        });
        return newObj;
    } else {
        return target;
    }
}

let obj = {
    num: 0,
    str: '',
    boolean: true,
    unf: undefined,
    nul: null,
    obj: {
        name: '我是一个对象',
        id: 1,
        qwe: {
            a: 1
        }
    },
    arr: [0, 1, 2, {b: 2}],
    date: new Date(0),
    reg: /我是一个正则/ig,
    [Symbol('1')]: 1,
    func() {
        console.log(123);
    }
};

obj.loop = obj;
let cloneObj = deepClone(obj);

obj.obj.id = 2;
console.log('obj', obj, obj.obj.id);
console.log('cloneObj', cloneObj, cloneObj.obj.id);

@zhuyuzhu
Copy link

zhuyuzhu commented May 17, 2021

使用深度优先遍历、WeakSet来实现。
注意:对象中Symbol属性名,必须用Symbol.for()的形式来作为属性名,而不能用Symbol()来做属性名,因为Symbol()的形式,根据唯一的特性,对象自身都无法访问该属性。

        var deepClone = function (target, weakset = new WeakSet()) {
            if (typeof target === 'object' && target !== null) { //包括数组、对象、类数组,Set、WeakSet、Map、WeakMap
                var o = Array.isArray(target) ? [] : {};
                if (weakset.has(target)) {
                    return target;
                } else {
                    weakset.add(target);
                    for (let prop in target) {
                        o[prop] = deepClone(target[prop], weakset)
                    }
                    var targetSymArr = Object.getOwnPropertySymbols(target);//获取target对象中的Symbol属性
                    if(targetSymArr.length > 0){
                        targetSymArr.forEach(item => {
                            o[item] = deepClone(target[item], weakset)
                        })
                        console.log(targetSymArr)
                    }
                    return o;
                }

            } else if (typeof target === "function") {
                return eval('(' + target.toString() + ')')
            } else {
                return target
            }
        }
        var obj = {
            a: 'a',
            m: {
                x: '12'
            }
        }
        var target1 = {
            a: 1,
            b: 'a',
            c: function () {
                console.log('c')
            },
            d: [1, 2, 3],
            f: obj,
            [Symbol.for('1')]: 1
        }
        target1.o = target1; //必须通过这种方式产生循环引用。如果在申明对象时,写在对象结构体中,无法赋值,值为undefined
        var result = deepClone(target1)
        console.log(result)

@Chorer
Copy link

Chorer commented Jul 7, 2021

// 获取数据类型
function getType(o) {
  return Object.prototype.toString.call(o).slice(8, -1);
}
// 判断是否是对象
function isObject(o) {
  return o !== null && (typeof o == "object" || typeof o == "function");
}
// 可遍历的对象列表
let list = ["Object", "Array", "Arguments", "Set", "Map"];

// 初始化可遍历的对象
function initCloneTarget(target) {
  return new target.constructor();
}

// 处理不可遍历的对象
function returnCloneTarget(target, type) {
  let _constructor = target.constructor;
  switch (type) {
    case "Error":
    case "Date":
    case "String":
    case "Number":
    case "Boolean":
      return new _constructor(target.valueOf());
    case "RegExp":
      return cloneReg(target);
    case "Symbol":
      return cloneSymbol(target);
    case "Function":
      return cloneFunction(target);
    default:
      return null;
  }
}

// 拷贝 symbol 对象
function cloneSymbol(target) {
  return Object(target.valueOf());
}

// 拷贝正则对象
function cloneReg(target) {
  const reFlags = /\w*$/;
  const result = new RegExp(target.source, reFlags.exec(target));
  result.lastIndex = target.lastIndex;
  return result;
}

// 拷贝函数对象
function cloneFunction(target) {
  return new Function(`return (${target})()`);
}

// 深拷贝函数
function deepClone(target, map = new WeakMap()) {
  // 如果是基本类型,直接返回
  if (!isObject(target)) return target;
  let type = getType(target);
  let cloneTarget;
  // 如果是可以遍历的对象,则进行初始化
  if (list.includes(type)) {
    cloneTarget = initCloneTarget(target);
  }
  // 否则就返回实例副本
  else {
    return returnCloneTarget(target, type);
  }
  // 解决循环引用
  if (map.has(target)) return map.get(target);
  map.set(target, cloneTarget);
  // 如果是 set
  if (type === "Set") {
    target.forEach((value) => {
      cloneTarget.add(deepClone(value, map));
    });
  }
  // 如果是 map
  if (type === "Map") {
    target.forEach((value, key) => {
      cloneTarget.set(key, deepClone(value, map));
    });
  }
  // 如果是对象字面量、数组或者类数组对象
  if (type === "Object" || type === "Array" || type === "Arguments") {
    Reflect.ownKeys(target).forEach((key) => {
      cloneTarget[key] = deepClone(target[key], map);
    });
  }
  return cloneTarget;
}

@mygod48
Copy link

mygod48 commented Nov 3, 2021

考虑了循环引用以及Symbol key,未考虑特殊对象(RegExp,Date等)
话说vscode居然把循环引用通过控制台的方式打印出来了,强。。。

function deepCopy(target, mem = new WeakMap()) {
    if ((target === null) || (typeof target !== 'object')) {
        return target;
    }

    if (mem.has(target)) {
        return mem.get(target);
    }

    if (Array.isArray(target)) {
        return target.map(item => {
            return deepCopy(item, mem);
        });
    } else {
        const copied = (target.constructor !== Object) ? Object.create(target.constructor.prototype) : {};
        mem.set(target, copied);

        [...Object.getOwnPropertyNames(target), ...Object.getOwnPropertySymbols(target)].reduce((currentObj, key) => {
            currentObj[key] = deepCopy(target[key], mem);
            return currentObj;
        }, copied);

        return copied;
    }
}

const a = {
    b: [1,2,3,4],
    c: 'str',
    d: {
        e: 1,
        f: Symbol(233)
    },
    [Symbol(234)]: 'g'
}

a.d.g = a;
a.b.push(a);

let b = deepCopy(a);

console.log(a);
console.log(b);

console.log(a.d === b.d);

结果:
image

@Rick-Lewis
Copy link

var cloneDeep = function(data, map = new WeakMap()){
	if(!data) null;
	let result;
	if(typeof data == 'object'){
		result = Object.prototype.toString.call(data) == '[object Object]' ? {} : [];
		if(map.get(data)){
			return map.get(data);
		}
		map.set(data, result);
		let temp = Reflect.ownKeys(data);
		for(let key of temp){
			result[key] = cloneDeep(data[key], map);
		}
	} else {
		result = data;
	}
	return result;
}

var obj = {
	a: 1,
	b: {
		c: 2,
		d: {
			e: 3
		}
	},
	f: undefined,
	[Symbol('g')]: 4,
	h: Symbol('i')
}
obj.f = obj;
var cloneObj = cloneDeep(obj);

@chao-liddell
Copy link

写个不用递归用循环的方式实现的版本吧

const getType = obj => Object.prototype.toString.call(obj).match(/\[object\s(.*)]/)[1]

function deepClone(obj) {
    let res = {}
    let stack = []
    let root = {
        parent: obj,
        prop: null,
        data: res
    }
    let wm = new WeakMap()
    stack.push(root)

    while (stack.length) {
        let item = stack.pop()
        Reflect.ownKeys(item.parent).forEach(key => {
            if (wm.get(item.parent[key])) {
                item.data[key] = wm.get(item.parent[key])
                return
            }
            switch (getType(item.parent[key])) {
                case 'Object': {
                    item.data[key] = {}
                    stack.push({
                        parent: item.parent[key],
                        prop: key,
                        data: item.data[key]
                    })
                    wm.set(item.parent[key], item.parent[key])
                    break
                }
                case 'Array': {
                    item.data[key] = []
                    stack.push({
                        parent: item.parent[key],
                        prop: key,
                        data: item.data[key]
                    })
                    wm.set(item.parent[key], item.parent[key])
                    break
                }
                case 'Date': {
                    item.data[key] = new Date(item.parent[key])
                    break
                }
                case 'RegExp': {
                    item.data[key] = new RegExp(item.parent[key])
                    break
                }
                default: {
                    item.data[key] = item.parent[key]
                }
            }
        })
    }

    return res
}


let obj = {
    num: 0,
    str: '',
    boolean: true,
    unf: undefined,
    nul: null,
    obj: {
        name: '我是一个对象',
        id: 1,
        qwe: {
            a: 1
        }
    },
    arr: [0, 1, 2, {b: 2}],
    date: new Date(0),
    reg: /我是一个正则/ig,
    [Symbol('1')]: 1,
    func() {
        console.log(123)
    }
};

obj.loop = obj

let cloneObj = deepClone(obj);

console.log('obj', obj);
console.log('cloneObj', cloneObj);

// 对比两个对象引用类型的值是相同
Object.keys(cloneObj).filter(key => key !== 'nul').forEach(key => {
    if (typeof cloneObj[key] === 'object' || typeof cloneObj[key] === 'function') {
        console.log(`${key}相同吗? `, cloneObj[key] === obj[key])
    }
})

函数拷贝不了,还有一些奇奇怪怪的引用类型也拷贝不了,一般情况应该没啥问题,其实拷贝函数有一种思路是用AST(手动狗头)

为什么要使用weakmap?求解

@slbyml
Copy link

slbyml commented Sep 1, 2022

https://juejin.cn/post/6990274836278804517#heading-7
深拷贝一定要考虑循环引用及爆栈等问题,可以参考这个

@ItachLuo
Copy link

function copyObj(obj) {
const res = {};
Reflect.ownKeys(obj).forEach((key) => {
if (typeof obj[key] === "object") {
res[key] = copyObj(obj[key]);
if (Array.isArray(obj[key])) {
res[key].length = obj[key].length;
res[key] = Array.from(res[key]);
}
} else {
res[key] = obj[key];
}
});
return res;
}
不知道这个怎么样

@LoveSleepMouse
Copy link

function deepClone(target, weakMap = new WeakMap()) {
        if (typeof target === null || typeof target !== "object") {
          return target;
        }

        if (target instanceof RegExp) return new RegExp(target);
        if (target instanceof Date) return new Date(target);

        if (weakMap.has(target)) return weakMap.get(target);

        let copy = Array.isArray(target) ? [] : {};
        weakMap.set(target, copy);
        const list = [
          ...Object.keys(target),
          ...Object.getOwnPropertySymbols(target),
        ];

        for (const key of list) {
          copy[key] = deepClone(target[key], weakMap);
        }
        return copy;
      }

@cike8899
Copy link

cike8899 commented Aug 31, 2023

一个不考虑其他数据类型的公共方法,基本满足大部分场景

function deepCopy(target, cache = new Set()) {
  if (typeof target !== 'object' || cache.has(target)) {
    return target
  }
  if (Array.isArray(target)) {
    target.map(t => {
      cache.add(t)
      return t
    })
  } else {
    return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((res, key) => {
      cache.add(target[key])
      res[key] = deepCopy(target[key], cache)
      return res
    }, target.constructor !== Object ? Object.create(target.constructor.prototype) : {})
  }
}

主要问题是

  1. symbol作为key,不会被遍历到,所以stringify和parse是不行的
  2. 有环引用,stringify和parse也会报错

我们另外用getOwnPropertySymbols可以获取symbol key可以解决问题1,用集合记忆曾经遍历过的对象可以解决问题2。当然,还有很多数据类型要独立去拷贝。比如拷贝一个RegExp,lodash是最全的数据类型拷贝了,有空可以研究一下

另外,如果不考虑用symbol做key,还有两种黑科技深拷贝,可以解决环引用的问题,比stringify和parse优雅强一些

function deepCopyByHistory(target) {
  const prev = history.state
  history.replaceState(target, document.title)
  const res = history.state
  history.replaceState(prev, document.title)
  return res
}

async function deepCopyByMessageChannel(target) {
  return new Promise(resolve => {
    const channel = new MessageChannel()
    channel.port2.onmessage = ev => resolve(ev.data)
    channel.port1.postMessage(target)
  }).then(data => data)
}

无论哪种方法,它们都有一个共性:失去了继承关系,所以剩下的需要我们手动补上去了,故有Object.create(target.constructor.prototype)的操作

一个不考虑其他数据类型的公共方法,基本满足大部分场景

function deepCopy(target, cache = new Set()) {
  if (typeof target !== 'object' || cache.has(target)) {
    return target
  }
  if (Array.isArray(target)) {
    target.map(t => {
      cache.add(t)
      return t
    })
  } else {
    return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((res, key) => {
      cache.add(target[key])
      res[key] = deepCopy(target[key], cache)
      return res
    }, target.constructor !== Object ? Object.create(target.constructor.prototype) : {})
  }
}

主要问题是

  1. symbol作为key,不会被遍历到,所以stringify和parse是不行的
  2. 有环引用,stringify和parse也会报错

我们另外用getOwnPropertySymbols可以获取symbol key可以解决问题1,用集合记忆曾经遍历过的对象可以解决问题2。当然,还有很多数据类型要独立去拷贝。比如拷贝一个RegExp,lodash是最全的数据类型拷贝了,有空可以研究一下

另外,如果不考虑用symbol做key,还有两种黑科技深拷贝,可以解决环引用的问题,比stringify和parse优雅强一些

function deepCopyByHistory(target) {
  const prev = history.state
  history.replaceState(target, document.title)
  const res = history.state
  history.replaceState(prev, document.title)
  return res
}

async function deepCopyByMessageChannel(target) {
  return new Promise(resolve => {
    const channel = new MessageChannel()
    channel.port2.onmessage = ev => resolve(ev.data)
    channel.port1.postMessage(target)
  }).then(data => data)
}

无论哪种方法,它们都有一个共性:失去了继承关系,所以剩下的需要我们手动补上去了,故有Object.create(target.constructor.prototype)的操作

对象里的数组也没有实现深拷贝

let obj = {
  arr: [0, 1, 2], 
};

const ret = deepCopy(obj);
console.info(ret.arr === obj.arr);

仍然返回true

@lastertd
Copy link

lastertd commented Sep 3, 2023

/**
 * @description 深度克隆
 * @param data 克隆目标
 * @param hash 标记目标是否被克隆过
 */
const cloneDeep = function <T>(data: T, hash: WeakMap<object, object> = new WeakMap()): T {

    // 特例判断, 另外函数也不需要被克隆
    if (data === null || typeof data !== 'object' || typeof data === 'function') {
        return data;
    }
    // 数组
    if (Array.isArray(data)) {
        const res: any[] = [];

        for (let index in data) {
            res[index] = cloneDeep(data[index], hash);
        }
        return res as T;
    }
    // 对象
    else {
        if (hash.get(data)) {   //如果目标已经被克隆过了,返回旧值
            return hash.get(data) as T;
        }

        const res: any = Object.create(Object.getPrototypeOf(data))     //保留对象原型
        hash.set(data, res);
        for (let key of Reflect.ownKeys(data)) {        //ownKeys 可以获取到对象的普通属性,不可枚举属性, symbol属性
            const item = Reflect.get(data, key);
            Reflect.set(res, key, cloneDeep(item, hash));
        }
        return res as T;
    }

}
export default cloneDeep

@negativeentropy9
Copy link

// 假设只考虑基本类型, symbol, 数组和对象以及对象循环引用
deepClone.cache = deepClone.cache || new Map();

function deepClone(data) {
  const type = Object.prototype.toString.call(data);
  let result;

  switch (type) {
    case "[object Array]":
      result = [];

      for (let item of data) {
        if (typeof item === "object") {
          result.push(deepClone(item));
        } else {
          result.push(item);
        }
      }
      break;
    case "[object Object]":
      if (deepClone.cache.get(data)) {
        return deepClone.cache.get(data);
      }

      result = {};
      deepClone.cache.set(data, result);

      // copy symbols
      const symbols = Object.getOwnPropertySymbols(data);

      for (let symbol of symbols) {
        result[symbol] = data[symbol];
      }

      for (let key in data) {
        if (typeof data[key] === "object") {
          result[key] = deepClone(data[key]);
        } else {
          result[key] = data[key];
        }
      }
      break;
    default:
  }

  return result;
}

const array = [
  { name: "Nancy", age: 20 },
  { name: "Jack", age: 21 },
];
const object = {
  name: "string",
  age: "number",
};
const loop = {
  id: 1,
  name: "id-1",
  children: [],
};
loop.children.push(loop);
const obj = {
  array,
  object,
  type: "test deepClone",
  priority: 1,
  [Symbol("symbol")]: "test symbol",
  loop,
};
const deepClonedObj = deepClone(obj);

console.log("obj", obj);
console.log("after deep clone: ", deepClonedObj);

array[0].name = "Alice";
object.age = "double";
loop.id = 2;
obj.priority = 2;

console.log("after change obj: ", obj, deepClonedObj);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests