【前端小tip】深拷贝不能处理函数的解决方法,文末包含所有深拷贝常见问题的解决方法

在开发过程中,我对对象进行深拷贝的时候常常使用序列化和反序列化,也就是

const newObj = JSON.parse(JSON.stringify(obj))

这个方法很好用,但是在最近我发现了一个弊端,就是它只能处理只含有基础类型属性和对象属性的对象,对函数没办法处理,例如以下对象

const obj = {
  name: "obj",
  friend: {
    name: "saber"
  },
  foo() {
    console.log("foo~")
  }
}

在用序列化和反序列化进行深拷贝后会输出:

{
  name: 'obj',
  friend: { name: 'saber' },
  foo: [Function: foo]
}	

很明显可以看出对函数的处理不是我们想要的结果,于是我们需要考虑用递归➕对象特判的方法实现

function deepClone(target) { 
    //检测数据类型
    if (typeof target !== 'object' || target === null) {
        // target 是 null ,或者不是对象和数组,说明是原始类型,直接返回
        return target;
    } else { 
        //创建一个容器,存储数组或者对象
        const result = Array.isArray(target) ? [] : {};
        //遍历target
        for(let key in target) { 
            //检测该属性是否为对象本身的属性(不能拷贝原型对象的属性)
            if(target.hasOwnProperty(key)) { 
            	//递归遍历子元素,直到能返回原始值
                result[key] = deepClone2(target[key]);
            }
        }
        return result;
    }
}

这样就可以成功处理函数了,输出如下:

{
  name: 'obj',
  friend: {
    name: 'saber'
  },
  foo: {}
}

在网上冲浪看看其他大神的解决办法,发现其他大神还发现了诸如循环引用引起无限递归、日期和正则表达式函数无法处理、Map和Set不好处理等问题,这里引用一个大神的方法,他几乎解决了所有常见问题,我也放进文章作为笔记:

   function deepClone(target) {
        // 创建一个 WeakMap 来保存已经拷贝过的对象,以防止循环引用
        const map = new Map();

        // 辅助函数:判断一个值是否为对象或函数
        function isObject(target) {
          return (
            (typeof target === "object" && target) || // 检查是否是非null的对象
            typeof target === "function" // 或者是函数
          );
        }

        // 主要的拷贝函数
        function clone(data) {
          // 基本类型直接返回
          if (!isObject(data)) {
            return data;
          }

          // 对于日期和正则对象,直接使用它们的构造函数创建新的实例
          if ([Date, RegExp].includes(data.constructor)) {
            return new data.constructor(data);
          }

          // 对于函数,创建一个新函数并返回
          if (typeof data === "function") {
            return new Function("return " + data.toString())();
          }

          // 检查该对象是否已被拷贝过
          const exist = map.get(data);
          if (exist) {
            return exist; // 如果已经拷贝过,直接返回之前的拷贝结果
          }

          // 如果数据是 Map 类型
          if (data instanceof Map) {
            const result = new Map();
            map.set(data, result); // 记录当前对象到 map
            data.forEach((val, key) => {
              // 对 Map 的每一个值进行深拷贝
              result.set(key, clone(val));
            });
            return result; // 返回新的 Map
          }

          // 如果数据是 Set 类型
          if (data instanceof Set) {
            const result = new Set();
            map.set(data, result); // 记录当前对象到 map
            data.forEach((val) => {
              // 对 Set 的每一个值进行深拷贝
              result.add(clone(val));
            });
            return result; // 返回新的 Set
          }

          // 获取对象的所有属性,包括 Symbol 类型和不可枚举的属性
          const keys = Reflect.ownKeys(data);
          // 获取对象所有属性的描述符
          const allDesc = Object.getOwnPropertyDescriptors(data);
          // 创建新的对象并继承原对象的原型链
          const result = Object.create(Object.getPrototypeOf(data), allDesc);

          map.set(data, result); // 记录当前对象到 map

          // 对象属性的深拷贝
          keys.forEach((key) => {
            result[key] = clone(data[key]);
          });

          return result; // 返回新的对象
        }

        return clone(target); // 开始深拷贝
      }

// 测试的sample对象
const sample = {
    // =========== 1.基础数据类型 ===========
    numberVal: 123,
    stringVal: "OpenAI",
    booleanVal: false,
    undef: undefined,
    nil: null,
    symKey: Symbol("key"),
    bigNumber: BigInt(1234567890n),
    // =========== 2.Object类型 ===========
    // 普通对象
    user: {
        firstName: "John",
        lastName: "Doe",
    },
    // 数组
    list: ["apple", "banana", "cherry"],
    // 函数
    display: function() {
        console.log("This is a display function");
    },
    // 日期
    birthDate: new Date(2000, 0, 1),
    // 正则
    pattern: new RegExp("/pattern/g"),
    // Map
    translations: new Map().set("hello", "hola"),
    // Set
    tags: new Set().add("fruit").add("food"),
    // =========== 3.其他 ===========
    [Symbol("unique")]: "uniqueValue",
};

// 4.添加不可枚举属性
Object.defineProperty(sample, "hidden", {
    enumerable: false,
    value: "Hidden Property",
});

// 5.设置原型对象
Object.setPrototypeOf(sample, {
    prototypeKey: "prototypeValue",
});

// 6.设置loop成循环引用的属性
sample.self = sample;

输出结果:
【前端小tip】深拷贝不能处理函数的解决方法,文末包含所有深拷贝常见问题的解决方法_第1张图片

出处点这里
探究JavaScript中的深拷贝:细节与实现

你可能感兴趣的:(前端)