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

JavaScript/数据拷贝 #4

Open
canvascat opened this issue Nov 27, 2020 · 1 comment
Open

JavaScript/数据拷贝 #4

canvascat opened this issue Nov 27, 2020 · 1 comment

Comments

@canvascat
Copy link
Owner

什么是拷贝?

首先来直观的感受一下什么是拷贝。

const arr = [1, 2, 3];
const newArr = arr;
newArr[0] = 100;

console.log(arr); // [100, 2, 3]

这是直接赋值的情况,不涉及任何拷贝。当改变 newArr 的时候,由于是同一个引用,arr 指向的值也跟着改变。

现在进行浅拷贝:

const arr = [1, 2, 3];
const newArr = arr.slice();
newArr[0] = 100;

console.log(arr); //[1, 2, 3]

当修改 newArr 的时候,arr 的值并不改变。什么原因?因为这里 newArr 是 arr 浅拷贝后的结果,newArr 和 arr 现在引用的已经不是同一块空间啦!

这就是浅拷贝!但是这又会带来一个潜在的问题:

const arr = [1, 2, { val: 4 }];
const newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr); // [ 1, 2, { val: 1000 } ]

当我们改变了 newArr 改变了第二个元素的 val 值,arr 也跟着变了。

这就是浅拷贝的限制所在了。它只能拷贝一层对象。如果有对象的嵌套,那么浅拷贝将无能为力。
深拷贝就是为了解决这个问题而生的,它能解决无限极的对象嵌套问题,实现彻底的拷贝。

浅拷贝的实现

  • Object.assign({}, ...sources)
  • ...扩展运算符(spread),如[...arr]{...obj}
  • [].concat(arr) - 拷贝数组
  • arr.slice() - 拷贝数组
  • 遍历
    const shallowClone = (target) => {
      if (typeof target === 'object' && target !== null) {
        const cloneTarget = Array.isArray(target) ? [] : {};
        for (let prop in target) {
          if (target.hasOwnProperty(prop)) {
            cloneTarget[prop] = target[prop];
          }
        }
        return cloneTarget;
      } else {
        return target;
      }
    };

深拷贝的实现

JSON.parseJSON.stringify

使用JSON.parse(JSON.stringify())能覆盖大多数的应用场景所需要的深拷贝,但是它也有许多问题:

  • 无法拷贝一些特殊的对象,诸如 RegExpDateSetMapFunction……
  • 值为 undefined 的 key 会丢失
  • 无法解决循环引用的问题。

关于 JSON.stringify 的坑可以看看这一篇文章 https://juejin.im/post/6844904016212672519

关于循环引用的例子:

image

递归

首先写一个不考虑循环引用的普通深拷贝:

const deepClone = (target) => {
  if (typeof target === 'object' && target !== null) {
    const cloneTarget = Array.isArray(target) ? [] : {};
    for (let prop in target) {
      if (target.hasOwnProperty(prop)) {
        cloneTarget[prop] = deepClone(target[prop]);
      }
    }
    return cloneTarget;
  } else {
    return target;
  }
};

解决循环引用

创建一个 cache,用来记录下已经拷贝过的对象,若已经拷贝过,直接返回。

function deepCopy(obj, cache = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj;

  if (cache.has(obj)) return cache.get(obj);

  const copy = Array.isArray(obj) ? [] : {};
  cache.set(obj, copy);
  Object.keys(obj).forEach(key => {
    copy[key] = deepCopy(obj[key], cache);
  });
  return copy;
}

结构化克隆算法(Structured Cloning Algorithm)

@canvascat
Copy link
Owner Author

针对特殊类型数据

  • 当参数为 DateRegExp 类型,则直接生成一个新的实例返回;
  • 利用 Object.getOwnPropertyDescriptors 方法获取对象所有属性以及对应的特性;
  • 使用 Object.create 创建一个新对象,并继承传入原对象的原型链;
  • 对于能够遍历对象的不可枚举属性以及 Symbol 类型,使用 Reflect.ownKeys 替换 Object.keys
function deepCopy(obj, cache = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (obj.constructor === Date) return new Date(obj);
  if (obj.constructor === RegExp) return new RegExp(obj);
  if (cache.has(obj)) return cache.get(obj);
  const descs = Object.getOwnPropertyDescriptors(obj);
  const copy = Object.create(Object.getPrototypeOf(obj), descs);
  cache.set(obj, copy);
  Reflect.ownKeys(obj).forEach(key => {
    copy[key] = deepCopy(obj[key], cache);
  });
  return copy;
}

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

No branches or pull requests

1 participant