Skip to content

深复制

浅拷贝和深拷贝说明

下列代码实现的是第二种方式,递归复制:

js
import deepCopy from './deep-copy'

const obj = {
  // =========== 1.基础数据类型 ===========
  num: 0, // number
  str: '', // string
  bool: true, // boolean
  unf: undefined, // undefined
  nul: null, // null
  sym: Symbol('sym'), // symbol
  bign: BigInt(1n), // bigint

  // =========== 2.Object类型 ===========
  // 普通对象
  obj: {
    name: '我是一个对象',
    id: 1,
  },
  // 数组
  arr: [0, 1, 2],
  // 函数
  func: function () {
    console.log('我是一个函数');
  },
  // 日期
  date: new Date(0),
  // 正则
  reg: new RegExp('/我是一个正则/ig'),
  // Map
  map: new Map().set('mapKey', 1),
  // Set
  set: new Set().add('set'),
  // =========== 3.其他 ===========
  [Symbol('1')]: 1, // Symbol作为key
};

console.log(obj);
console.log(deepCopy(obj));
js
// 浅拷贝与深拷贝:
// 基本数据储存在栈中,引用类型数据储存在堆之中
// 基本类型数据可直接复制,引用类型数据在赋值时,新变量没有获得新值,而是将指针指向此对象的堆位置
// 浅拷贝:拷贝了对象的引用地址,没有获得新值;深拷贝:获得新值,而不是获得引用地址

// 浅拷贝:let a = b;let a = Object.assign({},b)
// 深拷贝:let a = JSON.parse(JSON.stringify(b));
// 考虑多种引用类型的完整深拷贝:
import { isPlainObject, getObjectType, objectType } from "../modules/type";

/**
 * @method deepCopy
 * @description 深拷贝
 * @param {Object} obj 需要拷贝的对象
 * @return {Object} 返回复制的对象
 * @example
 * const newObject = deepCopy(oldObject)
 */
export const deepCopy = function (origin, map = new WeakMap()) {
    // 一、无数据 或者 类型为基本类型、函数则直接返回
    if (!origin || !isPlainObject(origin)) {
        return origin;
    }

    // 二、获取引用数据类型
    const type = getObjectType(origin);

    // 三、已经存在,则直接返回,循环引用
    if (map.has(origin)) {
        return map.get(origin);
    }

    // 四、根据类型处理
    // 1,正则或者Date类型
    if (type === objectType.reg || type === objectType.date) {
        const newObject = new origin.constructor(origin.valueOf());
        map.set(newObject);
        return newObject;
    }
    // 2,Set 类型
    if (type === objectType.set) {
        const newObject = new Set();
        for (const value of origin) {
            newObject.add(deepCopy(value, map));
        }
        map.set(newObject);
        return newObject;
    }
    // 3,Map类型
    if (type === objectType.map) {
        const newObject = new Map();
        for (const [key, value] of origin) {
            newObject.set(key, deepCopy(value, map));
        }
        map.set(newObject);
        return newObject;
    }

    // 4,Array 或者 Object 类型
    // 考虑了以Symbol作为key的情况
    const keys = Reflect.ownKeys(origin);
    // 获取描述符
    const descriptors = Object.getOwnPropertyDescriptors(origin);
    // 考虑原型链,继承原对象的原型,描述符
    const newObject = Object.create(Object.getPrototypeOf(origin), descriptors);

    map.set(newObject);
    keys.forEach((key) => {
        const value = origin[key];
        newObject[key] = deepCopy(value, map);
    });

    // 数组类型则还原
    return type === objectType.array ? Array.from(newObject) : newObject;
};
js
/** @module Type */

/**
 * @constant {Object} objectType 常用的引用类型
 * @property {String} objectType.object 常规对象
 * @property {String} objectType.array 数组
 * @property {String} objectType.date 日期
 * @property {String} objectType.reg 正则
 * @property {String} objectType.set Set
 * @property {String} objectType.map Map
 */
export const objectType = {
    object: "[object Object]",
    array: "[object Array]",
    date: "[object Date]",
    reg: "[object RegExp]",
    set: "[object Set]",
    map: "[object Map]",
};

/**
 * @description 获取对象引用类型,可以和 objectType 包含的类型进行比较
 * @method getObjectType
 * @param {Object} obj
 * @return {String}
 * @example
 * if(getObjectType(obj)===objectType.array){
 *
 * }
 */
export function getObjectType(obj) {
    return Object.prototype.toString.call(obj);
}

/**
 * @method isNumber
 * @description 判断传入值是否是数值
 * @param {any} val
 * @return {Boolean}
 */
export function isNumber(val) {
    return typeof val === "number";
}

/**
 * @method isUndefined
 * @description 判断传入值是否是未定义
 * @param {any} val
 * @return {Boolean}
 */
export function isUndefined(val) {
    return typeof val === "undefined";
}

/**
 * @method isPlainObject
 * @description 是否为普通对象
 * @param {any} val
 * @return {Boolean}
 */
export function isPlainObject(val) {
    return val && val.constructor.name === "Object";
}