# 浅深拷贝

# 浅拷贝

function cloneShallow(source) {
  var target = {};
  for (var key in source) {
    if (Object.prototype.hasOwnProperty.call(source, key)) {
      target[key] = source[key];
    }
  }
  return target;
}

# 实现 Object.assign

if (typeof Object.assign2 != 'function') {
  // 原生情况下挂载在 Object 上的属性是不可枚举的,但是直接在 Object 上挂载属性 a 之后是可枚举的,所以不使用Object.assign2 = function...
  // 这里必须使用 Object.defineProperty,并设置 enumerable: false 以及 writable: true, configurable: true。默认情况下不设置就是false
  Object.defineProperty(Object, 'assign2', {
    value: function(target) {
      'use strict';
      if (target == null) {
        // 判断参数是否正确
        throw new TypeError('Cannot convert undefined or null to Object');
      }
      // 原始类型被包装为对象
      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        // 判断参数是否正确
        if (nextSource != null) {
          // in 操作符会检查属性是否在对象及其 [[Prototype]] 原型链中
          // hasOwnProperty(..) 只会检查属性是否在 myObject 对象中,不会检查 [[Prototype]] 原型链
          for (var key in nextSource) {
            // 直接使用 myObject.hasOwnProperty(..) 是有问题的,因为有的对象可能没有连接到 Object.prototype
            // 上(比如通过 Object.create(null) 来创建),这种情况下,使用 myObject.hasOwnProperty(..) 就会失败。
            if (Object.prototype.hasOwnProperty.call(nextSource, key)) {
              to[key] = nextSource[key];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });
}

# 深拷贝

// 使用哈希表解决循环引用问题:
// 1、WeakMap只接受对象作为键名(null除外),且cloneDeep会先判断source是否是对象,所以满足条件
// 2、WeakMap的键名所指向的对象,不计入垃圾回收机制
function cloneDeep(source, hash = new WeakMap()) {
  // 判断是否是对象
  function isObject(obj) {
    // 除了普通对象还有数组,日期,函数等也都应该是可以拷贝的对象,所以不用下面一行的方法
    // return Object.prototype.toString.call(obj) === '[object Object]';
    return typeof obj === 'object' && obj != null;
  }
  // 非对象返回自身
  if (!isObject(source)) {
    return source;
  }
  // 查hash表,如果这个source已经被引用过了,直接返回
  // 即循环引用时,深节点又指向source对象,则直接返回哈希表记录的值
  if (hash.has(source)) {
    return hash.get(source);
  }

  // 区分拷贝数组和普通对象
  var target = Array.isArray(source) ? [] : {};
  // 1、哈希表记值,键为source,值为target。
  // 2、使用hash表同时还可以解决引用丢失问题:
  // 比如source中有2个key的值指向同一个对象source[key],不使用hash表拷贝后,每个key的值都是2个地址不相同的新对象
  // 使用哈希表后,因为source[key]被记录了,拷贝第二个值时会通过在哈希表查source[key]找到target直接返回第一个保存的target值
  hash.set(source, target);

  for (var key in source) {
    if (Object.prototype.hasOwnProperty.call(source, key)) {
      if (isObject(source[key])) {
        target[key] = cloneDeep(source[key]);
      } else {
        target[key] = source[key];
      }
    }
  }
}
// 这个实现还存在一个问题:递归爆栈
// 递归时函数执行上下文会一直被推入栈中,如果递归次数过多,最终会导致栈溢出问题
// 解决递归爆栈可以使用循环,在循环中使用广度优先遍历各个节点

写的不对,也不想写了,看别人的吧:https://juejin.cn/post/6844903929705136141 (opens new window)