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

发布于 2022-08-11 13:40:04 字数 132 浏览 134 评论 17

一个引用对象一般来说由两个部分组成:一个具名的 Handle,也就是我们所说的声明(如变量)和一个内部(不具名)的对象,也就是具名 Handle 的内部对象。它在 Manged Heap(托管堆)中分配,一般由新增引用对象的 New 方法是进行创建。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(17

当爱已成负担 2022-05-04 13:55:45

分享一个来自 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
}
清晨说ぺ晚安 2022-05-04 13:55:45

为什么没人用getOwnPropertyDescriptors

等往事风中吹 2022-05-04 13:55:45

@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 处理的方法可以

挽清梦 2022-05-04 13:55:45

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

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]: '

二智少女 2022-05-04 13:55:41
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)
岁月苍老的讽刺 2022-05-04 13:55:35

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

请恋爱 2022-05-04 13:54:51

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

花之痕靓丽 2022-05-04 13:54:50

野路子

缺点:无法拷贝函数 、Symbol

const deepClone = function (obj) {
    var str = JSON.stringify(obj);
    return JSON.parse(str);
}
还不是爱你 2022-05-04 13:53:56
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);

孤君无依 2022-05-04 13:51:21

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

const getType = obj => Object.prototype.toString.call(obj).match(/[objects(.*)]/)[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(手动狗头)

油饼 2022-05-04 13:45:43
  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())

┼──瘾|| 2022-05-04 13:36:36

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

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、函数名字

扮仙女 2022-05-04 13:35:32

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

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 是一个函数,函数没有被深拷贝
花落人断肠 2022-05-04 12:49:01
  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]);
故事和酒 2022-05-04 09:21:56

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

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)的操作

又怨 2022-05-02 20:05:27
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;
}

不知道有没有漏的。。

~没有更多了~

关于作者

一身软味

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

我们的影子

文章 0 评论 0

素年丶

文章 0 评论 0

南笙

文章 0 评论 0

18215568913

文章 0 评论 0

qq_xk7Ean

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文