Daily-Question icon indicating copy to clipboard operation
Daily-Question copied to clipboard

【Q202】如何实现一个深拷贝 (cloneDeep)

Open shfshanyue opened this issue 4 years ago • 19 comments

const obj = {
  re: /hello/,
  f () {},
  date: new Date(),
  map: new Map(),
  list: [1, 2, 3],
  a: 3,
  b: 4
}

cloneDeep(obj)

shfshanyue avatar Feb 21 '20 13:02 shfshanyue

const oldJson = { a: 1 }
const newJson = JSON.parse(JSON.stringify(oldJson))
oldJson.a = 2
console.log(oldJson) // {a: 2}
console.log(newJson) // {a: 1}

coder-eric avatar Nov 17 '20 08:11 coder-eric

function getType(obj){ return Object.prototype.toString.call(obj).slice(8,-1); } function cloneDeep(obj){ let target = {}; if(getType(obj)==='Object'){ for(let key in obj){ let item = obj[key]; target[key]=cloneDeep(item); } return target; }else if(getType(obj)==='Array'){ return obj.map(item => cloneDeep(item) ) }else{ return obj; } }

var obj = {foo:function(){},bar:1,name:'cat'}

var objClone = cloneDeep(obj)

miaooow avatar Jan 05 '21 10:01 miaooow

参考: clone

  1. 如何处理复杂对象,如 DateRegexp
  2. 如何处理循环引用

shfshanyue avatar May 27 '21 23:05 shfshanyue

const oldJson = { a: 1} const newJson = {} Object.assign(newJson, oldJson) oldJson.a = 2 console.log(oldJson) // {a: 2} console.log(newJson) // {a: 1}

对于深层的复杂类型,assign其实是浅拷贝 image

haiifeng avatar Jul 16 '21 07:07 haiifeng

/**
 * 深拷贝关注点:
 * 1. JavaScript内置对象的复制: Set、Map、Date、Regex等
 * 2. 循环引用问题
 * @param {*} object 
 * @returns 
 */
function deepClone(source, memory) {
  const isPrimitive = (value) => {
    return /Number|Boolean|String|Null|Undefined|Symbol|Function/.test(Object.prototype.toString.call(value));
  }
  let result = null;

  memory || (memory = new WeakMap());
  // 原始数据类型及函数
  if (isPrimitive(source)) {
    console.log('current copy is primitive', source);
    result = source;
  }
  // 数组
  else if (Array.isArray(source)) {
    result = source.map(value => deepClone(value, memory));
  }
  // 内置对象Date、Regex
  else if (Object.prototype.toString.call(source) === '[object Date]') {
    result = new Date(source);
  }
  else if (Object.prototype.toString.call(source) === '[object Regex]') {
    result = new RegExp(source);
  }
  // 内置对象Set、Map
  else if (Object.prototype.toString.call(source) === '[object Set]') {
    result = new Set();
    for (const value of source) {
      result.add(deepClone(value, memory));
    }
  }
  else if (Object.prototype.toString.call(source) === '[object Map]') {
    result = new Map();
    for (const [key, value] of source.entries()) {
      result.set(key, deepClone(value, memory));
    }
  }
  // 引用类型
  else {
    if (memory.has(source)) {
      result = memory.get(source);
    } else {
      result = Object.create(null);
      memory.set(source, result);
      Object.keys(source).forEach(key => {
        const value = source[key];
        result[key] = deepClone(value, memory);
      });
    }
  }
  return result;
}

haotie1990 avatar Jul 28 '21 11:07 haotie1990

(function (done) {
  if (!done) return;
  // 如何实现一个深拷贝 (cloneDeep)
  const obj = {
    re: /hello/,
    f() {},
    date: new Date(),
    map: new Map(),
    set: new Set(),
    list: [1, 2, 3],
    a: 3,
    b: 4,
    h: {
      name: "wby",
      age: 29,
    },
    e:undefined,
    d:null
  };
  let utils = getTypes()
  const newObj = cloneDeep(obj);
  console.log(newObj);
  console.log(obj.map === newObj.map);

  function getTypes(){
    let isTypes = {};
    function isTyping(typing) {
      return function (value) {
        return Object.prototype.toString.call(value) === `[object ${typing}]`;
      };
    }
    let types = ["Object", "Function", "RegExp", "Map", "Set", "Date", "Array","String"];
    for (let type of types) {
      isTypes[`is${type}`] = isTyping(type);
    }
    return isTypes;
  }
 
  function cloneDeep(obj, memory) {
    let target = Object.create(null);
    memory || (memory = new WeakMap());
    for (let key in obj) {
      let value = obj[key];
      if (typeof value !== "object" || value === null) {
        target[key] = value;
      } else {
        if (utils.isSet(value)) {
          target[key] = new Set();
          for (const v of value) {
            target[key].add(cloneDeep(v, memory));
          }
        } else if (utils.isMap(value)) {
          target[key] = new Map();
          for (const [k, v] of value.entries()) {
            target[key].set(k, cloneDeep(v, memory));
          }
        } else if (utils.isObject(value)) {
          target[key] = cloneDeep(value);
        } else {
          target[key] = new Object.prototype.constructor(value);
        }
      }
    }
    return target;
  }
})(1);

iceycc avatar Aug 21 '21 08:08 iceycc

 function deepCopy(obj) {
          var result = Array.isArray(obj) ? [] : {};
          for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
              if (typeof obj[key] === 'object') {
                result[key] = deepCopy(obj[key]);   //递归复制
              } else {
                result[key] = obj[key];
              }
            }
          }
          return result;
        }

illumi520 avatar Nov 03 '21 07:11 illumi520

const oldJson = { a: 1} const newJson = {} Object.assign(newJson, oldJson) oldJson.a = 2 console.log(oldJson) // {a: 2} console.log(newJson) // {a: 1} @kucy 对于数组等引用类型的属性值,Object.assign还是浅拷贝

jkLennon avatar Nov 15 '21 09:11 jkLennon

JS有原生的深拷贝API structuredClone:

const obj1 = { a: 1 };
const obj2 = structuredClone(obj1);

obj2.a = 2;

console.log(obj1); // { a: 1 }
console.log(obj2); // { a: 2 }

mrcaidev avatar May 02 '22 02:05 mrcaidev

        // 使用array from克隆
        let arr = [1, 2, 3, 4, 5, 6, [{ 1: 12, 2: 24 }]]
        let arr2 = Array.from(arr)
        console.log(arr === arr2)

        console.log(arr) // [1,2,3,4,5,6,{"1": 0,"2": 0}]
        console.log(arr2) // [1,2,3,4,5,6,{"1": 0,"2": 0}]

        // 解决实现不了深拷贝
        const deepClone = arr => Array.isArray(arr) ? Array.from(arr, deepClone) : arr
        let arr3=deepClone(arr)
        console.log(arr3) // [1,2,3,4,5,6,{"1": 12, "2": 24}]

        arr[6][0] = { 1: 00, 2: 00 }

QC2168 avatar May 24 '22 02:05 QC2168


// 使用扩展运算符实现深,浅拷贝
let arr = [1, 2, 3, 4, 5, 6, [{ 1: 12, 2: 24 }]]
let arr2 = [...arr]
console.log(arr === arr2)
console.log(arr) // [1,2,3,4,5,6,{"1": 0,"2": 0}]
console.log(arr2) // [1,2,3,4,5,6,{"1": 0,"2": 0}]
// 深拷贝
const deepClone = arr => arr.map(i => Array.isArray(i) ? deepClone(i) : i)
let arr3 = deepClone(arr)
arr[6][0] = { 1: 00, 2: 00 }
console.log(arr3)  // [1,2,3,4,5,6,{"1": 12, "2": 24}]

QC2168 avatar May 24 '22 02:05 QC2168

function deepClone(target, map = new WeakMap()) {
  if (typeof target !== "object") return target;

  if (map.has(target)) return map.get(target); // 解决循环引用问题

  const res = Array.isArray(target) ? [] : {};
  map.set(target, res);

  for (const key in target) {
    res[key] = deepClone(target[key], map);
  }
  return res;
}

// 测试
const obj = {
  foo: {
    bar: 2,
  },
  list: [1, 2, 3],
};
obj.obj = obj; // 循环引用

const obj2 = deepClone(obj);

console.log(obj, obj2);

obj.list[3] = 3333;
obj.foo.bar = 3;

console.log(obj, obj2);


4may-mcx avatar Aug 07 '22 03:08 4may-mcx

function deepClone(obj, map = new WeakMap()) {
    if (obj === null) return obj
    if (obj instanceof Date) return new Date(obj)
    if (obj instanceof RegExp) return new RegExp(obj)
    if (typeof obj !== 'object') return obj     
    if (map.has(obj)) return map.get(obj)   //解决循环引用
    const cloneobj = new obj.constructor()  //生成obj的对应类型
    map.set(obj, cloneobj)
    Reflect.ownKeys(obj).forEach((key) => {
        cloneobj[key] = deepClone(obj[key], map)
    })
    return cloneobj
}

coderWxs avatar Sep 08 '22 02:09 coderWxs

const obj = {
	re: /hello/,
  f() {},
  date: new Date(),
  map: new Map(),
  list: [1,2,3],
  a: 3,
  b: 4
}
function deepClone(target,map= new Map()) {
  if(target === null) return null
  if(Object.prototype.toString.call(target) === '[object RegExp]') {
    return new RegExp(target)
  }
  if(Object.prototype.toString.call(target) === '[object Date]') {
    return new Date(target)
  }
  if(typeof target === 'object') {
    let cloneTarget = Array.isArray(target)?[]:{}
    if(map.get(target)){
      return map.get(target)
    }
    map.set(target,cloneTarget)
    for(const key in target){
      cloneTarget[key] = deepClone(target[key],map)
    }
    return cloneTarget
  } else {
    return target
  }  
}

Yinzhuo19970516 avatar Apr 06 '23 03:04 Yinzhuo19970516

Array、Set、Map、Object中都可能出现循环引用。

const obj = {
    re: /hello/,
    f () {},
    date: new Date(),
    map: new Map(),
    list: [1, 2, 3],
    a: 3,
    b: 4
}

const obj2 = {loop2: obj, name: 'obj2'}
obj.loop = obj2


function deepClone(source, cache = new WeakMap()) {
    //原始类型或函数直接返回
    if (typeof source !== 'object') {
        return source
    }

    //加入缓存解决循环引用
    if (cache.has(source)) {
        return cache.get(source)
    }
    let res = new source.constructor()
    cache.set(source, res)
    
    //处理JS内置数据结构:Array、Map、Set、Object
    if (source instanceof Array) {
        source.forEach(v => {
            res.push(deepClone(v, cache))
        })
    }
    else if (source instanceof Map) {
        for (const [k, v] of source) {
            res.set(k, deepClone(v, cache))
        }
    }
    else if (source instanceof Set) {
        for (const v of source) {
            res.add(deepClone(v, cache))
        }
    }
    else if (Object.prototype.toString.call(source) == '[object Object]') {
        for(const key in source){
            res[key] = deepClone(source[key], cache)
        }
    }else {
        //处理自定义对象(需遵循协议new constructors时为深拷贝)
        res = new source.constructor(source)
    }

    return res
}

const newObj = deepClone(obj)
console.log(newObj)

xicYue avatar Apr 11 '23 11:04 xicYue

Array、Set、Map、Object中都可能出现循环引用。

const obj = {
    re: /hello/,
    f () {},
    date: new Date(),
    map: new Map(),
    list: [1, 2, 3],
    a: 3,
    b: 4
}

const obj2 = {loop2: obj, name: 'obj2'}
obj.loop = obj2


function deepClone(source, cache = new WeakMap()) {
    //原始类型或函数直接返回
    if (typeof source !== 'object') {
        return source
    }

    //加入缓存解决循环引用
    if (cache.has(source)) {
        return cache.get(source)
    }
    let res = new source.constructor()
    cache.set(source, res)
    
    //处理JS内置数据结构:Array、Map、Set、Object
    if (source instanceof Array) {
        source.forEach(v => {
            res.push(deepClone(v, cache))
        })
    }
    else if (source instanceof Map) {
        for (const [k, v] of source) {
            res.set(k, deepClone(v, cache))
        }
    }
    else if (source instanceof Set) {
        for (const v of source) {
            res.add(deepClone(v, cache))
        }
    }
    else if (Object.prototype.toString.call(source) == '[object Object]') {
        for(const key in source){
            res[key] = deepClone(source[key], cache)
        }
    }else {
        //处理自定义对象(需遵循协议new constructors时为深拷贝)
        res = new source.constructor(source)
    }

    return res
}

const newObj = deepClone(obj)
console.log(newObj)

typeof null 也是返回 'object' 的,建议都用 Object.prototype.toString.call() 判断吧

redsanjin-1 avatar Aug 29 '23 15:08 redsanjin-1

这是来自QQ邮箱的假期自动回复邮件。   您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。

xicYue avatar Aug 29 '23 15:08 xicYue

const object = {
  arr: [
    {
      b: {
        c: 3
      }
    },
    {
      b: {
        c: 4
      },
      d: 5,
      e: [
        {
          f: 6
        },
        {
          g: 7,
          h: {
            i: 8,
            j: [
              {
                k: 9
              },
              {
                l: 10,
                m: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "10"]
              }
            ]
          }
        }
      ]
    }
  ],
  func() {

  },
  date: new Date(),
  map: new Map(),
  set: new Set(),
  weakMap: new WeakMap(),
  weakSet: new WeakSet(),
  booleanT: true,
  booleanF: false,
  funcGet: (x) => {
    console.log(x)
    return x + 1
  },
  symbol: Symbol("x"),
  nul: null,
  undefin: undefined,
  nan: NaN,
  integrity: Infinity,
  number: 42,
  regexp: /hello/,
  bigint: BigInt("987654321"),
  str: "str",
  promise: new Promise((resolve, reject) => {
    resolve("promise 1")
  }),
  div: document.createElement('div',{
    id:'div',
    class:'div'
  }),
  generator: function* generatorFunc() {
    yield "yield 1"
  },
  err: new Error("error"),
}

function deepClone(origin, hashMap = new WeakMap()) {
  if (origin == undefined || typeof origin !== 'object'){
    return origin
  }

  if (origin instanceof Date){
    return new Date(origin.getTime())
  }

  if (origin instanceof RegExp){
    return new RegExp(origin)
  }

  if(origin instanceof HTMLElement){
    return origin.cloneNode(true)
  }

  if(origin instanceof Promise){
    return origin
  }

  if (hashMap.has(origin)){
    return hashMap.get(origin)
  }

  let targetClone = new origin.constructor()
  hashMap.set(origin, targetClone)
  Reflect.ownKeys(origin).forEach((key) => {
    targetClone[key] = deepClone(origin[key], hashMap)
  })

  return targetClone
}

hviwen avatar Feb 20 '24 03:02 hviwen

这是来自QQ邮箱的假期自动回复邮件。你好,我最近正在休假中,无法亲自回复你的邮件。我将在假期结束后,尽快给你回复。

loveminxo avatar Feb 20 '24 03:02 loveminxo