js拷贝函数

我们都知道js在copy的时候会有一些问题,特别是数据中存在引用类型的状态下,copy的数据又些类型可能会发生改变。以下是我们经常会用到的一些copy的方式,也都存在一些问题,我们需要按照不同的场景来使用合适是方式。

1. 

let obj2 = JSON.parse(JSON.stringify(obj1))

问题: 1.Date序列化的结果是字符串,还原之后还是字符串;

            2.Function、RegExp、Error对象和undefined值不能序列化和还原;

            3.NaN、Infinity、-Infinity序列化的结果是空;

            4.只能序列化对象可枚举属性;

            5. 会无意间改变一些数据类型,且无法copy Function 类型,

2. 

Object.assigin()

问题: 只能copy一层,嵌套的对象类型则不行

3. 递归遍历对象

function copy(dest){
  if(typeof dest === 'object'){
    if(!dest) return dest; // null
    var obj = dest.constructor();  // Object/Array
    for(var key in dest){
      obj[key] = copy(dest[key])
    }
    return obj;
  } else {
    return dest;
  }
}

这里调用原型链构造函数,限定条件相当于制执行Array()或Object()构造一个复杂空对象。同时这俩有个特点,是不需要new。整个函数实际上有个限定条件,就是只对Object和Array做深copy,也就是JSON的数据结构范围。其他对象不递归,也没处理循环引用。

问题: 1.递归引用的处理; 2. 特殊对象的处理(如Date); 3. Symbol属性的处理

<think>好的,我现在要帮用户解决JavaScript拷贝函数的问题。首先,我需要理解用户的需求是什么。他们可能想要了解如何正确实现一个深拷贝函数,避免浅拷贝带来的问题,比如对象属性引用同一个内存地址的情况。用户可能遇到过这样的问题:修改拷贝后的对象影响了原对象,所以需要深拷贝的方法。 接下来,我应该回顾深拷贝的基本概念,确保自己理解正确。深拷贝是创建一个新对象,完全复制原对象的所有属性,包括嵌套对象和数组,使得新旧对象完全独立,互不影响。而浅拷贝只复制一层属性,深层属性还是引用。 然后,用户可能知道一些简单的深拷贝方法,比如JSON.parse(JSON.stringify(obj)),但这种方法有局限性,比如不能处理函数、undefined、循环引用等。所以需要更健壮的实现方法。 接下来,我需要考虑如何实现一个深拷贝函数。可能需要递归地处理对象和数组,同时处理各种数据类型,比如Date、RegExp、Map、Set等。还要处理循环引用的问题,避免栈溢出。 然后,思考如何检测数据类型。使用Object.prototype.toString.call(obj)来准确判断类型,比如判断是否是对象、数组、日期、正则表达式等。 对于循环引用,可以使用WeakMap来保存已经拷贝过的对象,每次拷贝前检查是否已经存在,避免无限递归。WeakMap的键是原对象,值是拷贝后的对象。 另外,处理特殊对象比如Date和RegExp时,需要创建新的实例,而不是直接复制引用。比如,new Date(obj.getTime())来复制日期对象。 然后,考虑如何处理函数函数是否需要深拷贝?通常函数是不需要深拷贝的,直接引用即可,因为函数内部逻辑不会改变。但如果有特殊需求,可能需要用eval或者new Function来复制,但可能会有安全问题或作用域问题,所以一般不建议。 接下来,处理Map和Set这样的ES6数据结构。需要递归地拷贝它们的元素,并创建新的实例。 然后,编写代码的大致结构:创建一个deepClone函数,接受要拷贝的对象和一个哈希表(WeakMap)来存储已拷贝的对象。首先判断如果是基本数据类型,直接返回。然后检查哈希表中是否存在,存在则返回拷贝后的对象。然后根据不同的数据类型进行相应的处理,比如对象、数组、日期、正则、Map、Set等。对于普通对象,需要获取所有自身属性(包括Symbol属性),递归拷贝每个属性。 还需要考虑性能问题,递归深拷贝可能会比较慢,特别是对于大对象。但用户可能更关注正确性,所以性能是次要的。 最后,测试这个深拷贝函数是否能正确处理各种情况,包括嵌套对象、数组、循环引用、特殊对象等。比如: const obj = { a: 1, b: [2, { c: 3 }], d: new Date(), e: /regex/, f: function() {}, g: new Map([['key', 'value']]), h: new Set([1, 2, 3]), }; obj.self = obj; const cloned = deepClone(obj); console.log(cloned); 此时,cloned应该是一个完全独立的拷贝,不会影响原对象,且正确处理了各种类型和循环引用。 总结步骤: 1. 处理基本数据类型 2. 处理循环引用(使用WeakMap) 3. 处理特殊对象(Date、RegExp等) 4. 处理对象和数组的递归拷贝 5. 处理ES6数据结构如Map、Set 6. 处理Symbol属性和不可枚举属性(可能需要使用Reflect.ownKeys) 7. 处理函数(直接引用) 8. 测试各种情况 在解释的时候,需要分步骤说明,让用户能够理解每个部分的作用,并给出代码示例。同时,指出不同方法的优缺点,比如JSON方法的局限性,以及手写深拷贝函数的必要性。</think>JavaScript 的深拷贝函数用于创建对象的**完全独立副本**,所有嵌套属性和引用类型数据都会被递归复制。以下是实现深拷贝的逐步解析和推荐方法: --- ### 一、最简单的深拷贝方法(有局限性) ```javascript const deepCloneSimple = obj => JSON.parse(JSON.stringify(obj)); ``` - **优点**:代码简洁 - **缺点**: - 无法处理 `函数`、`undefined`、`Symbol` 类型 - 会丢失 `Date` 对象(转为字符串) - 无法处理 `循环引用`(如 `obj.self = obj`) --- ### 二、手写健壮的深拷贝函数 ```javascript function deepClone(target, hash = new WeakMap()) { // 处理非对象或 null if (typeof target !== 'object' || target === null) { return target; } // 处理循环引用 if (hash.has(target)) return hash.get(target); // 处理特殊对象类型 const constructor = target.constructor; let clone; switch (constructor) { case Date: clone = new constructor(target.getTime()); break; case RegExp: clone = new constructor(target); break; case Map: clone = new Map(); target.forEach((v, k) => clone.set(k, deepClone(v, hash))); break; case Set: clone = new Set(); target.forEach(v => clone.add(deepClone(v, hash))); break; default: clone = new constructor(); } hash.set(target, clone); // 递归处理属性 Reflect.ownKeys(target).forEach(key => { clone[key] = deepClone(target[key], hash); }); return clone; } ``` --- ### 三、关键实现解析 1. **基本类型处理** $$ \text{typeof target} \neq \text{'object'} \ \lor \ \text{target} = \text{null} \ \Rightarrow \ \text{直接返回} $$ 2. **循环引用检测** 使用 `WeakMap` 存储已拷贝对象,检测到重复引用时直接返回存储的副本 3. **特殊对象处理** - `Date`:通过时间戳重建 - `RegExp`:直接复制正则表达式 - `Map/Set`:遍历元素递归拷贝 4. **深层属性拷贝** 使用 `Reflect.ownKeys()` 获取所有属性(包括 Symbol 属性),递归拷贝每个属性 --- ### 四、使用示例 ```javascript const obj = { a: 1, b: [2, { c: 3 }], d: new Date(), e: /test/gi, f: new Map([['key', 'value']]), g: new Set([1, 2, 3]) }; obj.self = obj; // 循环引用 const cloned = deepClone(obj); console.log(cloned); // 完全独立副本 ``` --- ### 五、不同方案的对比 | 方法 | 保持原型链 | 处理函数 | 处理循环引用 | 处理特殊对象 | |---------------------|------------|----------|--------------|--------------| | `JSON` 方法 | ❌ | ❌ | ❌ | ❌ | | `lodash.cloneDeep` | ✅ | ✅ | ✅ | ✅ | | 手写实现 | ✅ | ✅ | ✅ | ✅ | **建议**:生产环境优先使用 `lodash.cloneDeep`,学习或定制需求时参考手写实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值