chst365's blog chst365's blog
首页
  • Git
  • 网络
  • 操作系统
  • 浏览器
  • webpack
  • JavaScript
  • TypeScript
  • 性能
  • 工程化
  • React
  • 编程题
  • React技术揭秘
  • 算法
  • Node
  • 编码解码
  • NodeJS系列
  • Linux系列
  • JavaScript系列
  • HTTP系列
  • GIT系列
  • ES6系列
  • 设计模式系列
  • CSS系列
  • 小程序系列
  • 数据结构与算法系列
  • React系列
  • Vue3系列
  • Vue系列
  • TypeScript系列
  • Webpack系列
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

chst365

DIV工程师
首页
  • Git
  • 网络
  • 操作系统
  • 浏览器
  • webpack
  • JavaScript
  • TypeScript
  • 性能
  • 工程化
  • React
  • 编程题
  • React技术揭秘
  • 算法
  • Node
  • 编码解码
  • NodeJS系列
  • Linux系列
  • JavaScript系列
  • HTTP系列
  • GIT系列
  • ES6系列
  • 设计模式系列
  • CSS系列
  • 小程序系列
  • 数据结构与算法系列
  • React系列
  • Vue3系列
  • Vue系列
  • TypeScript系列
  • Webpack系列
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 浏览器

  • webpack

  • TypeScript

  • 性能

  • 工程化

  • React

  • JavaScript

  • 编程题

    • 实现一个trim方法
    • 实现一个deepClone方法
      • 深拷贝和浅拷贝的定义
        • 浅拷贝
        • 深拷贝
      • 乞丐版
      • 基础版本
      • 考虑数组
      • 循环引用
      • 性能优化
      • 其他数据类型
    • 实现add(1)(2)(3)
    • 大数相加
    • 拍平数组flat
    • 实现防抖、节流函数
    • 反转字符串
    • 数组去重
    • 实现千位分隔符
    • 回文数
    • 实现一个模板引擎
    • 判断一个数是否是素数
    • 获取n以内所有的素数
  • React技术揭秘

  • 算法

  • 前端
  • 编程题
chst365
2022-09-01
目录

实现一个deepClone方法

# 深拷贝和浅拷贝的定义

# 浅拷贝

创建一新对象,有着原对象属性值的一份精确拷贝。若属性是基本类型,拷贝的是基本类型的值,若属性是引用类型,拷贝的是内存地址。 若其中一对象改变了这个地址,会影响到另一个对象。

# 深拷贝

将一个对象从内存中完整拷贝一份出来,从堆内存中开辟一新区域存放新对象,且修改新对象不影响原对象。

# 乞丐版

JSON.parse(JSON.stringify())
1

写法简单,可应对大部分场景,但拷贝其他引用类型、函数、循环引用等,是不行的

# 基础版本

function clone(target) {
    if (typeof target === 'object') {
        let cloneTarget = {};
        for (const key in target) {
            cloneTarget[key] = clone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
}
1
2
3
4
5
6
7
8
9
10
11

考虑的object,但是数组等依然不兼容

# 考虑数组

function clone(target) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        for (const key in target) {
            cloneTarget[key] = clone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
}
1
2
3
4
5
6
7
8
9
10
11

兼容数组,但是循环引用会导致死循环。

# 循环引用

解决循环引用问题,需额外开辟一个存储空间Map,存储当前对象和拷贝对象的对应关系。 当需拷贝当前对象时,先去存储空间中找,有无拷贝过此对象,若有直接返回,若无,继续拷贝。 注意: 这里用的是WeakMap WeakMap 对象时一组键/值对的集合,其中的键是弱引用。其键必是对象,而值可是任意的。 弱引用与强引用相对。一对象若只被弱引用所引用,则被认为是不可访问(或弱可访问),垃圾回收机制会自动帮我们回收。 当我们拷贝的对象非常庞大时,用Map会对内存造成非常大的额外消耗,且需手动消除Map属性才能释放这块内存,而WeakMap会帮我们解决。

function clone(target, map = new WeakMap()) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        if (map.get(target)) return target;
        map.set(target, cloneTarget);
        for (const key in target) {
            cloneTarget[key] = clone(target[key], map);
        }
        return cloneTarget;
    } else {
        return target;
    }
}
const target = {
    field1: 1,
    field2: undefined,
    field3: {
        child: 'child'
    },
    field4: [2, 4, 8]
};
target.target = target;
console.log(clone(target));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 性能优化

从图中了解,forin效率非常低,while效率非常高。

function clone(target, map = new WeakMap()) {
    if (typeof target === 'object') {
        const isArray = Array.isArray(target);
        let cloneTarget = isArray ? [] : {};
        if (map.get(target)) return target;
        map.set(target, cloneTarget);
        const keys = isArray ? undefined : Object.keys(target);
        forEach(keys || target, (value, key) => {
            if (keys) key = value;
            cloneTarget[key] = clone(target[key], map);
        });
        return cloneTarget;
    } else {
        return target;
    }
}
function forEach(array, iteratee) {
    let index = -1;
    const length = array.length;
    while (++index < length) {
        iteratee(array[index], index);
    }
    return array;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 其他数据类型

  • 合理的 判断引用类型
  • 获取数据类型
  • 可继续遍历的类型
  • 不可继续遍历的类型
  • 克隆函数
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';

const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const numberTag = '[object Number]';
const regexpTag = '[object RegExp]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const funcTag = '[object Function]';

const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];
function forEach(array, iteratee) {
    let index = -1;
    const length = array.length;
    while (++index < length) {
        iteratee(array[index], index);
    }
    return array;
}
function isObject(target) {
    const type = typeof target;
    return target !== null && (type === 'object' || type === 'function');
}
function getType(target) {
    return Object.prototype.toString.call(target);
}
function getInit(target) {
    const Ctor = target.constructor;
    return new Ctor();
}
function cloneSymbol(targe) {
    return Object(Symbol.prototype.valueOf.call(targe));
}
function cloneReg(targe) {
    const reFlags = /\w*$/;
    const result = new targe.constructor(targe.source, reFlags.exec(targe));
    result.lastIndex = targe.lastIndex;
    return result;
}
function cloneFunction(func) {
    const bodyReg = /(?<={)(.|\n)+(?=})/m;
    const paramReg = /(?<=\().+(?=\)\s+{)/;
    const funcString = func.toString();
    if (func.prototype) {
        const param = paramReg.exec(funcString);
        const body = bodyReg.exec(funcString);
        if (body) {
            if (param) {
                const paramArr = param[0].split(',');
                return new Function(...paramArr, body[0]);
            } else {
                return new Function(body[0]);
            }
        } else {
            return null;
        }
    } else {
        return eval(funcString);
    }
}
function cloneOtherType(targe, type) {
    const Ctor = targe.constructor;
    switch (type) {
        case boolTag:
        case numberTag:
        case stringTag:
        case errorTag:
        case dateTag:
            return new Ctor(targe);
        case regexpTag:
            return cloneReg(targe);
        case symbolTag:
            return cloneSymbol(targe);
        default:
            return null;
    }
}
function clone(target, map = new WeakMap()) {

    // 克隆原始类型
    if (!isObject(target)) return target;

    // 初始化
    const type = getType(target);
    let cloneTarget;
    if (deepTag.includes(type)) {
        cloneTarget = getInit(target, type);
    } else {
        return cloneOtherType(target, type);
    }

    // 防止循环引用
    if (map.get(target)) return target;
    map.set(target, cloneTarget);

    // 克隆set
    if (type === setTag) {
        target.forEach(value => {
            cloneTarget.add(clone(value));
        });
        return cloneTarget;
    }

    // 克隆map
    if (type === mapTag) {
        target.forEach((value, key) => {
            cloneTarget.set(key, clone(value));
        });
        return cloneTarget;
    }

    // 克隆对象和数组
    const keys = type === arrayTag ? undefined : Object.keys(target);
    forEach(keys || target, (value, key) => {
        if (keys) key = value;
        cloneTarget[key] = clone(target[key], map);
    });

    return cloneTarget;
}

const map = new Map();
map.set('key', 'value');
map.set('ConardLi', 'code秘密花园');

const set = new Set();
set.add('ConardLi');
set.add('code秘密花园');

const target = {
    field1: 1,
    field2: undefined,
    field3: {
        child: 'child'
    },
    field4: [2, 4, 8],
    empty: null,
    map,
    set,
    bool: new Boolean(true),
    num: new Number(2),
    str: new String(2),
    symbol: Object(Symbol(1)),
    date: new Date(),
    reg: /\d+/,
    error: new Error(),
    func1: () => {
        console.log('code秘密花园');
    },
    func2: function (a, b) {
        return a + b;
    }
};

console.log(clone(target));
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#前端#编程题
上次更新: 2022/09/07, 15:54:26
实现一个trim方法
实现add(1)(2)(3)

← 实现一个trim方法 实现add(1)(2)(3)→

最近更新
01
面试官
03-27
02
this&指针&作用域&闭包
03-27
03
前端
03-27
更多文章>
Theme by Vdoing | Copyright © 2019-2025 chst365 | 豫ICP备17031889号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式