Preface
Hi everyone, this is Lin Sanxin. I was chatting with the leader a few days ago about deep copy
- leader: Do you know how to copy an object?
- I know! Isn't it a deep copy of
?
- leader: Then how did you
deep copy of 161b1b2ab39ab4?
- Me: I
JSON.parse(JSON.stringfy(obj))
eat 061b1b2ab39acc directly - leader: Brother, if
lodash
have time,deepClone
and see 061b1b2ab39af1 in 061b1b2ab39af0 to see how others achieve it
Haha, indeed, deep copy has many application scenarios in daily development, and it is also very important. It is necessary to
deep copy method. So how can we write a qualified
deep copy method? In other words, how can we write a
deep copy method?
Deep copy && Shallow copy
Let's first talk about what is a deep copy and what is a shallow copy.
Shallow copy
The so-called shallow copy means that only the outermost layer is copied, and the contents inside are still the same reference
// 浅拷贝
const a = { name: 'sunshine_lin', age: 23, arr: [] }
const b = {}
for (let key in a){
b[key] = a[key]
}
console.log(b) // { name: 'sunshine_lin', age: 23, arr: [] }
console.log(b === a) // false
console.log(b.arr === a.arr) // true
deep copy
Deep copy, you copy an object to another new variable, this new variable points to a new heap memory address
// 深拷贝
function deepClone(target) {
// ...实现深拷贝
}
const a = { name: 'sunshine_lin', age: 23, arr: [] }
const b = deepClone(a)
console.log(b) // { name: 'sunshine_lin', age: 23, arr: [] }
console.log(b === a) // false
console.log(b.arr === a.arr) // false
Gold version
I believe that most people will do this when they implement deep copy.
function deepClone(target) {
return JSON.parse(JSON.stringify(target))
}
const a = { name: 'sunshine_lin', age: 23 }
const b = deepClone(a)
console.log(b) // { name: 'sunshine_lin', age: 23 }
console.log(b === a) // false
Although it is fine to use this way most of the time, there are still many shortcomings in this method.
- 1. There is a field value of
undefined
object, and the field will disappear directly after conversion - 2. If the object has a field value of
RegExp
object, the field value will become {} after conversion - 3. If the object has a field value of
NaN、+-Infinity
, the field value becomes null after conversion - 4. If the object has
reference, the conversion will report an error directly
Platinum version
Since it is a deep copy of the object, I can create an empty object and copy the values of the original objects that need to be copied one by one! ! !
function deepClone(target) {
const temp = {}
for (const key in target) {
temp[key] = target[key]
}
return temp
}
const a = { name: 'sunshine_lin', age: 23 }
const b = deepClone(a)
console.log(b) // { name: 'sunshine_lin', age: 23 }
console.log(b === a) // false
But in fact, the above approach is imperfect, because we don't know how many layers of objects we want to copy. . When everyone hears "I don't know how many layers there are," they must think of recursion. Yes, just use recursion.
function deepClone(target) {
// 基本数据类型直接返回
if (typeof target !== 'object') {
return target
}
// 引用数据类型特殊处理
const temp = {}
for (const key in target) {
// 递归
temp[key] = deepClone(target[key])
}
return temp
}
const a = {
name: 'sunshine_lin',
age: 23,
hobbies: { sports: '篮球',tv: '雍正王朝' }
}
const b = deepClone(a)
console.log(b)
// {
// name: 'sunshine_lin',
// age: 23,
// hobbies: { sports: '篮球', tv: '雍正王朝' }
// }
console.log(b === a) // false
Diamond version
Earlier, we only considered the case of objects, but did not consider the case of arrays, so we have to add array conditions
function deepClone(target) {
// 基本数据类型直接返回
if (typeof target !== 'object') {
return target
}
// 引用数据类型特殊处理
// 判断数组还是对象
const temp = Array.isArray(target) ? [] : {}
for (const key in target) {
// 递归
temp[key] = deepClone(target[key])
}
return temp
}
const a = {
name: 'sunshine_lin',
age: 23,
hobbies: { sports: '篮球', tv: '雍正王朝' },
works: ['2020', '2021']
}
const b = deepClone(a)
console.log(b)
// {
// name: 'sunshine_lin',
// age: 23,
// hobbies: { sports: '篮球', tv: '雍正王朝' },
// works: ['2020', '2021']
// }
console.log(b === a) // false
Star version
None of the previously implemented methods solve the problem of ring reference
JSON.parse(JSON.stringify(target))
errorTypeError: Converting circular structure to JSON
, which means thatcannot handle ring references
recursive method
Maximum call stack size exceeded
error 061b1b2ab39ead, which means thatcannot finish recursively, and the stack bursts
// 环引用
const a = {}
a.key = a
How to resolve ring references? In fact, it is not difficult to say, you need to use the ES6 data structure Map
- Every time a reference data type is traversed, it is treated as
key
placed inMap
. The correspondingvalue
is the newly createdobject temp
- Every time a reference data type is traversed, look for the corresponding
key
. If there is, it means that the object has been registered before, and now it is encountered for the second time, it must be a ring reference.key
getsvalue
and returnsvalue
function deepClone(target, map = new Map()) {
// 基本数据类型直接返回
if (typeof target !== 'object') {
return target
}
// 引用数据类型特殊处理
// 判断数组还是对象
const temp = Array.isArray(target) ? [] : {}
+ if (map.get(target)) {
+ // 已存在则直接返回
+ return map.get(target)
+ }
+ // 不存在则第一次设置
+ map.set(target, temp)
for (const key in target) {
// 递归
temp[key] = deepClone(target[key], map)
}
return temp
}
const a = {
name: 'sunshine_lin',
age: 23,
hobbies: { sports: '篮球', tv: '雍正王朝' },
works: ['2020', '2021']
}
a.key = a // 环引用
const b = deepClone(a)
console.log(b)
// {
// name: 'sunshine_lin',
// age: 23,
// hobbies: { sports: '篮球', tv: '雍正王朝' },
// works: [ '2020', '2021' ],
// key: [Circular]
// }
console.log(b === a) // false
King version
We just realized
copy of basic data types
refers to the
array, object in the data type
But in fact, reference data types are more than just arrays and objects. We have to solve the following problem of copying reference types. How to judge the respective types of each reference data type? Can use Object.prototype.toString.call()
type | toString | result |
---|---|---|
Map | Object.prototype.toString.call(new Map()) | [object Map] |
Set | Object.prototype.toString.call(new Set()) | [object Set] |
Array | Object.prototype.toString.call([]) | [object Array] |
Object | Object.prototype.toString.call({}) | [object Object] |
Symbol | Object.prototype.toString.call(Symbol()) | [object Symbol] |
RegExp | Object.prototype.toString.call(new RegExp()) | [object RegExp] |
Function | Object.prototype.toString.call(function() {}) | [object Function] |
Let's first divide the above reference type data into two categories
- Traversable data types
- Non-traversable data types
// 可遍历的类型
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
// 不可遍历类型
const symbolTag = '[object Symbol]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';
// 将可遍历类型存在一个数组里
const canForArr = ['[object Map]', '[object Set]',
'[object Array]', '[object Object]']
// 将不可遍历类型存在一个数组
const noForArr = ['[object Symbol]', '[object RegExp]', '[object Function]']
// 判断类型的函数
function checkType(target) {
return Object.prototype.toString.call(target)
}
// 判断引用类型的temp
function checkTemp(target) {
const c = target.constructor
return new c()
}
Traversable reference types
Mainly deal with the following four types
- Map
- Set
- Object
Array
function deepClone(target, map = new Map()) { // 获取类型 const type = checkType(target) // 基本数据类型直接返回 if (!canForArr.concat(noForArr).includes(type)) { return target } // 引用数据类型特殊处理 const temp = checkTemp(target) if (map.get(target)) { // 已存在则直接返回 return map.get(target) } // 不存在则第一次设置 map.set(target, temp) // 处理Map类型 if (type === mapTag) { target.forEach((value, key) => { temp.set(key, deepClone(value, map)) }) return temp } // 处理Set类型 if (type === setTag) { target.forEach(value => { temp.add(deepClone(value, map)) }) return temp } // 处理数据和对象 for (const key in target) { // 递归 temp[key] = deepClone(target[key], map) } return temp } const a = { name: 'sunshine_lin', age: 23, hobbies: { sports: '篮球', tv: '雍正王朝' }, works: ['2020', '2021'], map: new Map([['haha', 111], ['xixi', 222]]), set: new Set([1, 2, 3]), } a.key = a // 环引用 const b = deepClone(a) console.log(b) // { // name: 'sunshine_lin', // age: 23, // hobbies: { sports: '篮球', tv: '雍正王朝' }, // works: [ '2020', '2021' ], // map: Map { 'haha' => 111, 'xixi' => 222 }, // set: Set { 1, 2, 3 }, // key: [Circular] // } console.log(b === a) // false
Non-traversable reference types
Mainly deal with the following types
- Symbol
- RegExp
- Function
Write out the methods of copying these three types first
// 拷贝Function的方法
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);
}
}
// 拷贝Symbol的方法
function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe));
}
// 拷贝RegExp的方法
function cloneReg(targe) {
const reFlags = /\w*$/;
const result = new targe.constructor(targe.source, reFlags.exec(targe));
result.lastIndex = targe.lastIndex;
return result;
}
Final version
function deepClone(target, map = new Map()) {
// 获取类型
const type = checkType(target)
// 基本数据类型直接返回
if (!canForArr.concat(noForArr).includes(type)) return target
// 判断Function,RegExp,Symbol
+ if (type === funcTag) return cloneFunction(target)
+ if (type === regexpTag) return cloneReg(target)
+ if (type === symbolTag) return cloneSymbol(target)
// 引用数据类型特殊处理
const temp = checkTemp(target)
if (map.get(target)) {
// 已存在则直接返回
return map.get(target)
}
// 不存在则第一次设置
map.set(target, temp)
// 处理Map类型
if (type === mapTag) {
target.forEach((value, key) => {
temp.set(key, deepClone(value, map))
})
return temp
}
// 处理Set类型
if (type === setTag) {
target.forEach(value => {
temp.add(deepClone(value, map))
})
return temp
}
// 处理数据和对象
for (const key in target) {
// 递归
temp[key] = deepClone(target[key], map)
}
return temp
}
const a = {
name: 'sunshine_lin',
age: 23,
hobbies: { sports: '篮球', tv: '雍正王朝' },
works: ['2020', '2021'],
map: new Map([['haha', 111], ['xixi', 222]]),
set: new Set([1, 2, 3]),
func: (name, age) => `${name}今年${age}岁啦!!!`,
sym: Symbol(123),
reg: new RegExp(/haha/g),
}
a.key = a // 环引用
const b = deepClone(a)
console.log(b)
// {
// name: 'sunshine_lin',
// age: 23,
// hobbies: { sports: '篮球', tv: '雍正王朝' },
// works: [ '2020', '2021' ],
// map: Map { 'haha' => 111, 'xixi' => 222 },
// set: Set { 1, 2, 3 },
// func: [Function],
// sym: [Symbol: Symbol(123)],
// reg: /haha/g,
// key: [Circular]
// }
console.log(b === a) // false
Concluding remarks
If you think this article is of little help to you, please give me a thumbs up and encourage Lin Sanxin haha. Or you can join my fishing group, we study hard together, ah ah ah ah ah, I will mock interviews regularly, resume guidance, answer questions and answer questions, let's learn from each other and make progress together! !
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。