深拷贝

深拷贝和浅拷贝的区别:浅拷贝 : 当对于一个对象进行浅拷贝的时候,会创建一个新的对象,新对象包含有旧对象的所有属性,当属性值为基本类型时,拷贝的就是这个基本类型的值,当属性值为引用类型的时候,拷贝的是这个引用类型的内存地址深拷贝:将一个对象从内存中完整的拷贝出来,开辟一个新的区域存储新对象,并且修改新对象不会影响旧对象

实现深拷贝

  • 使用 JSON.parse(JSON.stringify())这种最简单的实现深拷贝的方法同时存在许多的缺点
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    let obj = {
    date: new Date(),
    nan: NaN,
    function: new Function(),
    undefined: undefined,
    regexp: new RegExp('\\w+'),
    symbol: Symbol('symbol')
    }

    function deepClone(obj) {
    return JSON.parse(JSON.stringify(obj))
    }

    let cloneObj = deepClone(obj)

    console.log(cloneObj)
    // 打印结果如下
    { date: '2021-02-26T07:22:37.173Z', nan: null, regexp: {} }
    使用这种方法进行深拷贝的时候,对于上面一些特殊的属性值,会出现拷贝异常的情况:
    • undefined, symbol, 函数 会被忽略掉
    • NaN 会被转换为 null
    • regexp 会被转换为 空对象
    • date 对象会被转换为日期字符串
    同时,无法拷贝循环引用的对象
  • 一种 cloneDeep 的方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function cloneDeep(value) {
    if(typeof value === 'object' && value !== null) {
    const isArray = Array.isArray(value)
    let result = isArray ? [] : {}
    if (isArray) {
    value.forEach(val => {
    result.push(cloneDeep(val))
    })
    } else {
    for (let k in value) {
    result[k] = cloneDeep(value[k])
    }
    }
    return result
    } else return value
    }

lodash 中的 cloneDeep 方法

入口

1
2
3
4
5
6
7
import baseClone from './.internal/baseClone.js'
const CLONE_DEEP_FLAG = 1
const CLONE_SYMBOLS_FLAG = 4

function cloneDeep(value) {
return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG)
}
入口调用 baseClone 文件中的 baseClone 方法,向这个方法中传入了两个数据:value: 要进行复制的数据CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG: 掩码,表明使用 baseClone 来进行深拷贝以及 symbol 数据类型的拷贝

baseClone

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
/**
* The base implementation of `clone` and `cloneDeep` which tracks
* traversed objects.
*
* @private
* @param {*} value 需要克隆的数据
* @param {number} bitmask 掩码标识
* 1 - Deep clone
* 2 - Flatten inherited properties
* 4 - Clone symbols
* @param {Function} [customizer] 定制 clone 的方法
* @param {string} [key] value 的属性 key
* @param {Object} [object] 值的父对象
* @param {Object} [stack] 用来追踪遍历的对象
* @returns {*} Returns the cloned value.
*/
function baseClone(value, bitmask, customizer, key, object, stack) {
let result
// 三种克隆的配置值
const isDeep = bitmask & CLONE_DEEP_FLAG
const isFlat = bitmask & CLONE_FLAT_FLAG
const isFull = bitmask & CLONE_SYMBOLS_FLAG
// 使用定制化的 clone 方法
if (customizer) {
result = object ? customizer(value, key, object, stack) : customizer(value)
}
// 定制化的 clone 方法执行之后,返回结果
if (result !== undefined) {
return result
}
// 当非 引用类型的时候,返回值
if (!isObject(value)) {
return value
}
const isArr = Array.isArray(value)
// 获取当前数据的类型
const tag = getTag(value)
if (isArr) {
// 初始化 clone 数组
result = initCloneArray(value)
if (!isDeep) {
return copyArray(value, result)
}
} else {
const isFunc = typeof value === 'function'

// 当 value 是一种 buffer 数据的时候
if (isBuffer(value)) {
return cloneBuffer(value, isDeep)
}
// 当当前的数据类型为 对象,参数对象,函数的时候
if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
// 当是一个函数的时候,初始化为一个空对象,否则,调用 initCloneObject
result = (isFlat || isFunc) ? {} : initCloneObject(value)
if (!isDeep) {
return isFlat
? copySymbolsIn(value, copyObject(value, keysIn(value), result))
: copySymbols(value, Object.assign(result, value))
}
// 对于 typeof value === 'object' 但是 调用 `getTag` 方法并不是严格对象的值的处理
// 例如:let n = new Number()
// typeof n === 'number' but Object.prototype.toString.call(n) === '[object Number]'
} else {
if (isFunc || !cloneableTags[tag]) {
return object ? value : {}
}
result = initCloneByTag(value, tag, isDeep)
}
}
// Check for circular references and return its corresponding clone.
// 创建一个 Stack 的数据结果
// 使用 stack 目的可以检查循环引用,返回对应的 clone 数据
stack || (stack = new Stack)
const stacked = stack.get(value)
if (stacked) {
return stacked
}
stack.set(value, result)

// 当当前数据为 map 结构的时候
// result 为 map 数据
if (tag == mapTag) {
value.forEach((subValue, key) => {
result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack))
})
return result
}
// 当 当前数据为。set 数据的时候
if (tag == setTag) {
value.forEach((subValue) => {
result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack))
})
return result
}

if (isTypedArray(value)) {
return result
}
//
const keysFunc = isFull
? (isFlat ? getAllKeysIn : getAllKeys)
: (isFlat ? keysIn : keys)

const props = isArr ? undefined : keysFunc(value)
arrayEach(props || value, (subValue, key) => {
if (props) {
key = subValue
subValue = value[key]
}
// Recursively populate clone (susceptible to call stack limits).
// 递归式的 clone
assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack))
})
return result
}

export default baseClone
}
getTag
getTag 方法用来获取元素的类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const toString = Object.prototype.toString

/**
* Gets the `toStringTag` of `value`.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/
function getTag(value) {
if (value == null) {
return value === undefined ? '[object Undefined]' : '[object Null]'
}
return toString.call(value)
}

export default getTag
initCloneObject
initCloneObject 用来初始化克隆对象:
1
2
3
4
5
6
7
8
9
// object 用来初始化克隆的对象
function initCloneObject(object) {
// 当 object 为非原型对象的时候,返回一个对象,这个对象的继承 obj 的原型
return (typeof object.constructor === 'function' && !isPrototype(object))
? Object.create(Object.getPrototypeOf(object))
: {}
}

export default initCloneObject
isPrototype
判断 value 是否为原型,如果是,返回 true对于 原型 对象上面包含有 constructor 属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const objectProto = Object.prototype

/**
* Checks if `value` is likely a prototype object.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
*/
function isPrototype(value) {
const Ctor = value && value.constructor
const proto = (typeof Ctor === 'function' && Ctor.prototype) || objectProto

return value === proto
}
对于边界条件的处理:如果仅仅按照下面的代码进行判断是有问题的:
1
2
3
4
5
function isPrototype(vlaue) {
const Ctor = value && value.constructor
const proto = (typeof Ctor === 'function' && Ctor.prototype)
return value === proto
}
如果我们传入一个 false, isPrototype 返回的结果为 true, 与结果不符合,因此设置 一个 objectProto 来防止这种情况