• 你对深拷贝的理解是只停留在JSON.parse(JSON.stringify()) ?
  • 发布于 1个月前
  • 72 热度
    0 评论
  • 果酱
  • 20 粉丝 55 篇博客
  •   

想必很多人对深拷贝的概念只停留在JSON.parse(JSON.stringify()),但他有个致命的缺陷,就是无法拷贝undefined、function等类型的值。


下面让我们浅浅的了解下深拷贝:深拷贝是一种拷贝对象的方式,它会创建一个完全独立于原始对象的新对象。深拷贝不仅复制了对象的引用,还复制了对象的所有内部属性和子对象。这意味着源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。


在实现深拷贝时,需要在堆内存中重新开辟一块空间,存放原对象的值,并让栈中的引用指向这块新的内存地址。这样,新旧对象就不会再相互影响。我们先来看看深拷贝的逻辑图:

在进行深拷贝时,不仅复制对象的引用(即地址),还会复制对象本身及其所有子对象。这样,新的对象将完全独立于原始对象。在这张图中,如果我们对obj1进行深拷贝,这些对象与原始对象在内容上相同但完全独立。让我们来对深拷贝递归实现的梳理吧,用最简单的代码实现最全面的深拷贝。

1.函数的定义
function deepCopy(val, hash = new WeakMap()) {  
   //堆代码 duidaima.com
}
这里定义了一个名为deepCopy的函数,它接受两个参数:val(要拷贝的值)和hash(一个WeakMap对象,用于存储已经拷贝过的对象和它们的拷贝,默认为一个新的WeakMap)。

2.类型判断
if (typeof val !== 'object' || val === null) {    
    return val;    
}
首先,函数检查val的类型。如果val不是对象或者为null,则直接返回val本身,因为基本数据类型如(字符串、数字、布尔值等)是按值传递的,不需要深度拷贝。

3.循环引用检查
if (hash.has(val)) {    
    return hash.get(val);    
}
接下来函数检查val是否已经在hash中存在。如果存在,说明之前已经拷贝过这个对象,直接返回拷贝的引用,避免无限递归。

4.创建拷贝
let copy;  
    if (Array.isArray(val)) {  
        copy = [];  
    } else if (val instanceof Date) {  
        copy = new Date(val.getTime());  
    } else if (val instanceof RegExp) {  
        let pattern = val.valueOf()
        let flags = ''
        flags += pattern.global ? 'g' : '';
        flags += pattern.ignoreCase ? 'i' : '';
        flags += pattern.multiline ? 'm' : '';
        copy = new RegExp(pattern.source, flags);
    } else if (typeof val === 'function') {  
        // 对于函数,我们直接返回引用(浅拷贝)  
        copy = val;  
    } else {  
        copy = {};  
    }  
根据val的类型,函数创建不同的拷贝:
如果val是数组,创建一个新的空数组copy。
如果val是Date对象,通过new Date(val.getTime())创建一个新的Date对象,并设置相同的时间。
如果val是RegExp对象,通过val.valueOf()获取正则表达式的描述(但这实际上是错误的,因为valueOf在RegExp对象上并不返回源字符串和标志。应直接使用val.source获取源字符串),并手动构建标志字符串,最后通过new RegExp(pattern.source, flags)创建一个新的RegExp对象。
如果val是函数,直接返回val的引用(浅拷贝),因为函数在JavaScript中是不可变的。

如果val是普通对象,创建一个新的空对象copy。


5. 存入哈希表
hash.set(val, copy);
在创建拷贝后,将原始对象val和它的拷贝copy存入hash中,以便后续处理可能出现的循环引用。

6.递归复制对象的属性
for (let key in val) {    
    if (val.hasOwnProperty(key)) {    
        copy[key] = deepCopy(val[key], hash);    
    }    
}
使用for...in循环遍历val的所有可枚举属性,并对每个属性递归调用deepCopy函数,将结果赋值给copy的相应属性。
最后,函数返回创建的拷贝对象copy。

例子校验
// 使用示例  
let val = {  
  a: 1,  
  b: {  
      c: 2,  
      d: [3, 4]  
  }  
};  

let copiedval = deepCopy(val);  

console.log(copiedval); // { a: 1, b: { c: 2, d: [3, 4] } }  
console.log(copiedval.b.d === val.b.d); // false,说明是深拷贝
没毛病!
完整代码
function deepCopy(val, hash = new WeakMap()) {  
    // 如果传入的不是对象或数组,直接返回  
    if (typeof val !== 'valect' || val === null) {  
        return val;  
    }  
  
    // 如果对象已经存在于哈希表中,直接返回引用  
    if (hash.has(val)) {  
        return hash.get(val);  
    }  
  
    // 创建一个新的拷贝  
    let copy;  
    if (Array.isArray(val)) {  
        copy = [];  
    } else if (val instanceof Date) {  
        copy = new Date(val.getTime());  
    } else if (val instanceof RegExp) {  
        let pattern = val.valueOf()
        let flags = ''
        flags += pattern.global ? 'g' : '';
        flags += pattern.ignoreCase ? 'i' : '';
        flags += pattern.multiline ? 'm' : '';
        copy = new RegExp(pattern.source, flags);
    } else if (typeof val === 'function') {  
        // 对于函数,我们直接返回引用(浅拷贝)  
        copy = val;  
    } else {  
        copy = {};  
    }  
  
    // 将新对象存入哈希表  
    hash.set(val, copy);  
     // 堆代码 duidaima.com
    // 递归复制对象的属性  
    for (let key in val) {  
        if (val.hasOwnProperty(key)) {  
            copy[key] = deepCopy(val[key], hash);  
        }  
    }  
  
    return copy;  
}
手撕深拷贝是面试经常遇到的问题,了解并掌握深拷贝,对自身开发也是有很大帮助!
用户评论