Advanced-Frontend / Daily-Interview-Question

我是依扬(木易杨),公众号「高级前端进阶」作者,每天搞定一道前端大厂面试题,祝大家天天进步,一年后会看到不一样的自己。
https://muyiy.cn/question/
27.41k stars 3.29k forks source link

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

Open yygmind opened 5 years ago

kungithub commented 5 years ago

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 commented 5 years ago

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

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 commented 5 years ago
  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 commented 5 years ago

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

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 commented 5 years ago

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

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 commented 5 years ago
  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 commented 5 years ago

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


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 commented 5 years ago
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 commented 5 years ago

野路子

缺点:无法拷贝函数 、Symbol

const deepClone = function (obj) {
    var str = JSON.stringify(obj);
    return JSON.parse(str);
}
NathanHan1 commented 5 years ago

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

NathanHan1 commented 5 years ago

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

Gemiry commented 5 years ago

image @lhyt

jiao2563719877 commented 5 years ago

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 commented 5 years ago

分享一个来自 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 commented 5 years ago

为什么没人用getOwnPropertyDescriptors

yft commented 5 years ago

@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);

感觉环状数据之前这个里面 https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/10 处理的方法可以

yaodongyi commented 5 years ago

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

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 commented 5 years ago

不考虑正则、函数等奇怪类型的拷贝,满足大多数深度拷贝需求 定制需求如下: 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 commented 4 years ago
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 commented 4 years ago

记录一下简单做的

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 commented 4 years ago
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 commented 4 years ago
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 commented 4 years ago

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 commented 4 years ago

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

korylee commented 4 years ago

少了个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 commented 4 years ago
// 咱来个简单易懂的
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 commented 4 years ago

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

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 commented 4 years ago
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 commented 4 years ago

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 commented 4 years ago

记录一下

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 commented 4 years ago

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

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 commented 3 years ago

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 commented 3 years ago

来源于拉勾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 commented 3 years ago
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 commented 3 years ago
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 commented 3 years ago
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 commented 3 years ago

使用深度优先遍历、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 commented 3 years ago
// 获取数据类型
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 commented 2 years ago

考虑了循环引用以及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 commented 2 years ago
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 commented 2 years ago

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

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 commented 2 years ago

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

ItachLuo commented 1 year ago

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 commented 1 year ago
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 commented 1 year ago

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

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 commented 1 year ago
/**
 * @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 commented 4 months ago
// 假设只考虑基本类型, 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);