javascript 浅拷贝 & 深拷贝(简记)

90 阅读3分钟

变量存储

基本数据类型: stringnumberbooleannullundefinedsymbol  
    基本类型是值存储在栈中的,值是不可变的,无论是复制还是拼接等都会创建一个新的值
引用数据类型:ObjectArrayMapSetDate...
    引用类型是值存储在堆中的,其引用(即物理地址)是存储在栈中,我们通常也是通过其引用访问和修改存储在堆中的值。所以,对于对象类型来说,赋值表达式仅仅使新的变量和被复制的对象指向同一块内存空间而已,所以深度拷贝的话,需要对每个对象都创建一块新的内存空间存值
    

浅拷贝

即仅拷贝对象的第一层属性,若第一层属性的值是个普通对象,那么拷贝后对象和原对象他们的属性值是相同的

深拷贝

即完全复制对象,拷贝后的对象与原对象不再存在任何联系

浅拷贝 & 深拷贝代码实现

/** 获取对象类型 */
function getType(val) {
  return Object.prototype.toString.call(val).toLowerCase().slice(8, -1);
}
/** 判断对象ctx上是否存在prop属性,但不在ctx原型上 */
function hasOwnProperty(ctx, prop) {
  return Object.prototype.hasOwnProperty.call(ctx, prop);
}

/** 拷贝数组 */
function cloneArray(arr) {
  return arr.map(item => deepClone(item));
}
/** 拷贝普通对象 */
function clonePlainObject(obj, map, options = {}) {
  if (map.has(obj)) return map.get(obj);

  const newObj = {};
  if (!(options.isBreakPropsReferenced && map.size)) map.set(obj, newObj);

  for (key in obj) {
    if (hasOwnProperty(obj, key)) newObj[key] = deepClone(obj[key], map, options);
  }
  cloneSymbolProps(obj, newObj);

  return newObj;
}
/** 克隆symbol属性 */
function cloneSymbolProps(obj, newObj) {
  if (getType(Object.getOwnPropertySymbols) === 'function') {
    const symKeys = Object.getOwnPropertySymbols(obj);
    symKeys.forEach(key => newObj[key] = deepClone(obj[key]));
  }
}

/** 深度拷贝 */
function deepClone(source, map = new Map(), options = {}) {
  const type = getType(source);
  let result = source;
  switch (type) {
    case 'object':
      result = clonePlainObject(source, map, { ...options });
      break;
    case 'array':
      result = cloneArray(source);
      break;
    case 'map':
      result = new Map(source.entries());
      break;
    case 'set':
      result = new Set(source.values());
      break;
    case 'date':
      result = new Date(source);
      break;
    case 'regexp':
      result = new RegExp(source);
      break;
    default:
      result = source;
      break;
  }
  return result;
}

/**
 * @description 浅层拷贝
 * @param {*} source 
 */
function shadowClone(source) {
  const type = getType(source);
  let result;
  switch (type) {
    case 'object':
      result = {};
      if (typeof Reflect === 'object' && getType(Reflect.ownKeys) === 'function') {
        result = Reflect.ownKeys(source).reduce((prev, cur) => {
          prev[cur] = source[cur];
          return prev;
        }, {});
      } else {
        for (key in source) {
          if (hasOwnProperty(source, key)) {
            result[key] = source[key];
          }
        }
        if (getType(Object.getOwnPropertySymbols) === 'function') {
          const symKeys = Object.getOwnPropertySymbols(source);
          symKeys.forEach(key => result[key] = source[key]);
        }
      }
      break;
    case 'array':
      result = [...source];
      break;
    case 'map':
      result = new Map(source.entries());
      break;
    case 'set':
      result = new Set(source.values());
      break;
    case 'date':
      result = new Date(source);
      break;
    case 'regexp':
      result = new RegExp(source);
      break;
    default:
      result = source;
      break;
  }
  return result;
}

/**
 * 
 * @template {{ 
 * isDeep: boolean; // 是否需要深度拷贝 (默认false)
 * isBreakPropsReferenced: boolean; // 是否需要断开对象内部属性对同一个对象的引用关系(默认false)
 * }} IOption
 * @param {any} source 需要拷贝的目标对象
 * @param {IOption} options 拷贝条件
 * @returns 
 */
function clone(source, options = {}) {
  /** map 用于解决循环引用导致的栈溢出问题、对象内部属性引用同一个对象拷贝后引用关系丢失问题 */
  let map = new Map();
  
  let result;
  if (options.isDeep) {
    result = deepClone(source, map, options);
  } else {
    result = shadowClone(source);
  }
  map.clear();
  map = null;
  return result;
}

// test code
const log = console.log;
/** 测试复杂对象 */
// const obj = {
//   a: {
//     b: {
//       c: 100
//     }
//   },
//   m: [1,2,3],
//   n: 200,
//   k: 'good',
//   h: null,
//   p: undefined,
//   w: [{
//       x: {
//         a: 300,
//         b: 400
//       }
//     }, {
//       u: 900
//     }]
// };
// const res = clone(obj, { isDeep: true });
// log(res.a === obj.a);
// log(res);

/** 测试循环引用 */
// const obj = {};
// obj.b = obj;
// const res = clone(obj, true);
// log(res);

/** 测试对象内不同属性指向一个对象,拷贝后引用关系是否还保持 */
// const o = { m: 300 };
// const obj = {};
// obj.x = o;
// obj.y = o;
// const res = clone(obj, {
//   isDeep: true,
//   // isBreakPropsReferenced: true,
// });
// log(res.x === res.y);
// log(res);

/** 测试 Symbol 拷贝 */
// const symbol = Symbol('good');
// const obj = {
//   m: 11,
//   n: 22,
//   [symbol]: 33,
//   x: symbol,
// }
// const res = clone(obj, { isDeep: true });
// log(obj);
// log(res);
// log(res[symbol] === obj[symbol]);
// log(res.x === obj.x);

/** 测试 Map 拷贝 */
// const obj = {
//   m: 11,
//   n: 22,
//   x: new Map([['a', 10], ['b', 20]]),
// }
// const res = clone(obj, { isDeep: true });
// res.x.set('c', 30);
// log(obj);
// log(res);

/** 测试 Set 拷贝*/
// const obj = {
//   m: 11,
//   n: 22,
//   x: new Set([1,2,3,4,5]),
// }
// const res = clone(obj, { isDeep: true });
// res.x.add(6);
// log(obj);
// log(res);

/** 测试 Date 拷贝*/
// const obj = {
//   m: 11,
//   n: 22,
//   x: new Date(),
// }
// const res = clone(obj, { isDeep: true });
// log(obj);
// log(res.x === obj.x);
// log(res.x.getTime() === obj.x.getTime());

/** 测试 RegExp 拷贝*/
// const obj = {
//   m: 11,
//   n: 22,
//   x: new RegExp(/[1-9]/g),
// }
// const res = clone(obj, { isDeep: true });
// log(obj);
// log(res.x === obj.x);
// log(res.x.toString() === obj.x.toString());