Dreamer Dreamer
首页
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)

lycpan233

白日梦想家
首页
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)
  • Vue

  • JavaScript

    • JavaScript部分特殊值判断记录
    • JavaScript各遍历方法对比
    • 防抖函数 Debounce
    • JavaScript 手撕深拷贝
      • 懒人版思路
      • 手撕思路
    • JavaScript位运算应用场景
    • 浅谈Cookie和Session的区别
    • js 快速创建二维数组并初始化
    • js 获取变量准确类型
  • 前端
  • JavaScript
lycpan233
2024-05-23
目录

JavaScript 手撕深拷贝

# 深拷贝

JS 虽然没有指针,但是存在引用类型,除了基本数据类型 number、string、boolean、null、undefined、symbol、bigint,其他都是引用类型。常见的引用类型包括: object、array、function、map、set、date、regexp 等。

深拷贝主要考校的是对于引用类型的拷贝,对于基本数据类型,直接赋值即可。

因此深拷贝的实现一般需要考虑这几个方面:

  1. 基本数据类型。

  2. 引用类型的。

    2.1 object、array、function

    2.2 map、set

    2.3 date、regexp

  3. 循环引用。

除此之外还有一些进阶选项:

  1. symbol 作为 key 时的支持。
  2. 原型拷贝。
  3. 不可枚举属性属性拷贝。
  4. 函数的拷贝。
  5. 二进制数组,如 Int8Array、Uint8Array、Uint8ClampedArray 等

# 实现代码

本代码中未对『进阶选项』进行支持

function deepCopy(data) {
    // 定义 Map 防止循环引用
    const weakMap = new WeakMap();

    // 定义 func 判断值的类型
    function type(value) {
        return value === null ? 'Null' : value === undefined ? 'Undefined' : Object.prototype.toString.call(value).slice(8, -1);
    }

    function clone(value) {
        // 实际 copy 方法
        const copy = function copy(copiedValue) {
            if (weakMap.has(value)) { 
                return weakMap.get(value);
            }
            weakMap.set(value, copiedValue);
            if (value instanceof Map) {
                value.forEach((el, key) => {
                    copiedValue.set(key, clone(el));
                });
            } else if (value instanceof Set) {
                value.forEach(el => {
                    copiedValue.add(clone(el));
                });
            } else {
                for (const key in value) {
                    if(bject.prototype.hasOwnProperty.call(value,key)) {
                        copiedValue[key] = clone(value[key]);
                    }
                }
            }
            return copiedValue;
        };

        // 判断值的类型
        switch (type(value)) {
            case 'Object': return copy(Object.create(Object.getPrototypeOf(value)));
            case 'Array': return copy([]);
            case 'RegExp':
            case 'Date': return new value.constructor(value);
            case 'Map': return copy(new Map());
            case 'Set': return new Set(value);
            default: return value;
        }
    }

    return clone(data);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 测试对象

// 测试的obj对象
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
};

// 4.添加不可枚举属性
Object.defineProperty(obj, 'innumerable', {
    enumerable: false,
    value: '不可枚举属性'
});

// 5.设置原型对象
Object.setPrototypeOf(obj, {
    proto: 'proto'
})

// 6.设置loop成循环引用的属性
obj.loop = obj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 实现思路

# 懒人版思路

 JSON.parse(JSON.stringify(obj))
1

通过 JSON 转换一次即可实现深拷贝,但是实际使用中,有很多情况无法处理,酌情处理。

# 手撕思路

  1. 基本类型,基本类型不涉及引用,直接赋值即可。

  2. function,没想到 function 如何产生变更(不认为其有需要深拷贝的场景),其实也可以直接返回。不放心可以采用下面的方式生成新函数。

    new Function('return' + func.toString())()

  3. array 数组如果仅涉及基本数据类型,进行解构赋值即可。

    [ ...array ]

  4. map、set 遍历 new 一个新的进行赋值。

  5. object 对象,也可以创建一个新的对象,遍历属性进行重新赋值,遍历会遇到以下问题。

    • 创建对象的需要原型保持一致

      Object.create(Object.getPrototypeOf(obj))

    • 遍历对象时,会遍历到原型链上的属性,需要过滤掉。

      Object.prototype.hasOwnProperty()

    • 如何遍历不可枚举的对象。

      Reflect.ownKeys(obj)

    • 遍历以后遇到引用类型,需要递归处理。可能会引出第6点的循环引用问题。

  6. 循环引用,用 map、array 等数据结构存储已经处理过的对象,如果碰撞直接返回。这里推荐 weakMap。

# 相关链接

ramda - 0.27.1 (opens new window)

JavaScript深拷贝看这篇就行了!(实现完美的ES6+版本) - CSDN (opens new window)

编辑 (opens new window)
上次更新: 2025/04/15, 03:48:14
防抖函数 Debounce
JavaScript位运算应用场景

← 防抖函数 Debounce JavaScript位运算应用场景→

最近更新
01
docker基础概念
02-26
02
js 获取变量准确类型
02-19
03
Mysql SQL 优化思路
02-18
更多文章>
Theme by Vdoing | Copyright © 2023-2025 Dreamer | MIT License
粤ICP备2025379918号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式