高级前端
js
【Q202】如何实现一个深拷贝 (cloneDeep)

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

更多描述

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

Issue 欢迎在 Gtihub Issue 中回答此问题: Issue 203 (opens in a new tab)

Author 回答者: coder-eric (opens in a new tab)

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

Author 回答者: miaooow (opens in a new tab)

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)

Author 回答者: shfshanyue (opens in a new tab)

参考: clone (opens in a new tab)

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

Author 回答者: haiifeng (opens in a new tab)

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

Author 回答者: haotie1990 (opens in a new tab)

/**
 * 深拷贝关注点:
 * 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;
}

Author 回答者: iceycc (opens in a new tab)

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

Author 回答者: illumi520 (opens in a new tab)

 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;
        }

Author 回答者: jkLennon (opens in a new tab)

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还是浅拷贝

Author 回答者: mrcaidev (opens in a new tab)

JS有原生的深拷贝API structuredClone:

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

Author 回答者: QC2168 (opens in a new tab)

// 使用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 };

Author 回答者: QC2168 (opens in a new tab)

// 使用扩展运算符实现深,浅拷贝
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}]

Author 回答者: 4may-mcx (opens in a new tab)

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

Author 回答者: coderWxs (opens in a new tab)

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
}

> Author
回答者: [Yinzhuo19970516](https://github.com/Yinzhuo19970516)


```js
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
  }
}

Author 回答者: xicYue (opens in a new tab)

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

Author 回答者: redsanjin-1 (opens in a new tab)

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() 判断吧

Author 回答者: xicYue (opens in a new tab)

这是来自QQ邮箱的假期自动回复邮件。

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

Author 回答者: hviwen (opens in a new tab)

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;
}

Author 回答者: loveminxo (opens in a new tab)

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