This article introduces a simple deep copy handwritten version, focus on solving the deep copy of the circular reference and function reference the same problem, will big guy please bypass
First, data types
1. Basic data types
String, Boolean, Null, undefined, Symbol, Bigint
Bigint is a recently introduced basic data type
2. Reference data types
Object, Array, Function, etc
Data types are not the focus of this article
Here is the object to copy. The rest of the code will use $obj directly and will not declare it againCopy the code
var $obj = {
func: function () {
console.log('this is function')},date: new Date(),
symbol: Symbol(),
a: null.b: undefined.c: {
a: 1
},
e: new RegExp('regexp'),
f: new Error('error')
}
$obj.c.d = $obj
Copy the code
Shallow copy
1. What is shallow copy
For an object, a full copy of the data is made for the first attribute value of the base data type, and the memory address is copied for the reference type. It does copy very shallow [chuckles]
2, implementation,
Object.assign()
let obj1 = {
name: 'yang'.res: {
value: 123}}let obj2 = Object.assign({}, obj1)
obj2.res.value = 456
console.log(obj2) // {name: "yang", res: {value: 456}}
console.log(obj1) // {name: "yang", res: {value: 456}}
obj2.name = 'haha'
console.log(obj2) // {name: "haha", res: {value: 456}}
console.log(obj1) // {name: "yang", res: {value: 456}}
Copy the code
A grammarSpread
let obj1 = {
name: 'yang'.res: {
value: 123}}let{... obj2} = obj1 obj2.res.value =456
console.log(obj2) // {name: "yang", res: {value: 456}}
console.log(obj1) // {name: "yang", res: {value: 456}}
obj2.name = 'haha'
console.log(obj2) // {name: "haha", res: {value: 456}}
console.log(obj1) // {name: "yang", res: {value: 456}}
Copy the code
Array.prototype.slice
const arr1 = [
'yang',
{
value: 123}];const arr2 = arr1.slice(0);
arr2[1].value = 456;
console.log(arr2); // ["yang", {value: 456}]
console.log(arr1); // ["yang", {value: 456}]
arr2[0] = 'haha';
console.log(arr2); // ["haha", {value: 456}]
console.log(arr1); // ["yang", {value: 456}]
Copy the code
Array.prototype.concat
const arr1 = [
'yang',
{
value: 123}];const arr2 = [].concat(arr1);
arr2[1].value = 456;
console.log(arr2); // ["yang", {value: 456}]
console.log(arr1); // ["yang", {value: 456}]
arr2[0] = 'haha';
console.log(arr2); // ["haha", {value: 456}]
console.log(arr1); // ["yang", {value: 456}]
Copy the code
In fact, for arrays, as long as the original array is not modified, return a new array can achieve shallow copy, such as map, filter, reduce, etcCopy the code
Deep copy
1. What is deep copy
A deep copy is a copy of both the basic and reference data types without sharing data
2, implementation,
Violent versionJSON.parse(JSON.stringify(object))
let obj = JSON.parse(JSON.stringify($obj))
console.log(obj) // Cannot resolve circular references
/* VM348:1 Uncaught TypeError: Converting circular structure to JSON at JSON.stringify (
) at
:1:17 */
delete $obj.c.d
let obj = JSON.parse(JSON.stringify($obj))
console.log(obj) // Most attributes are missing
/ * {a: null c: {a: 1} the date: "the 2020-04-05 T09:51:32. 610 z" e: {} f: {}} * /
Copy the code
Existing problems:
1. Undefined is ignored
2, will ignore symbol
3. Functions cannot be serialized
4. Cannot resolve objects referenced by loop
Error handling new Date()
Cannot handle regex
7, cannot handle new Error()
First edition Base version
Recursively traverses object properties
function deepCopy (obj) {
if (obj === null || typeofobj ! = ='object') {
return obj
}
let copy = Array.isArray(obj) ? [] : {}
Object.keys(obj).forEach(v= > {
copy[key] = deepCopy(obj[key])
})
return copy
}
deepCopy($obj)
/* VM601:23 Uncaught RangeError: Maximum call stack size exceeded at
:23:30 at Array.forEach (
) at deepCopy (
:23:22) */
delete $obj.c.d
deepCopy($obj)
/ * {a: null b: undefined c: {a: 1} date: e: {} {} f: {} func: ƒ () symbol: symbol ()} * /
Copy the code
The problems are:
1. Cannot resolve objects referenced by loop
New Date() cannot be handled correctly
Cannot handle regex
4, cannot handle new Error()
The second edition addresses circular references
The solution is to store objects and object properties in an array to see if there are any objects already traversed in the next iteration. If there are, the object will be returned directly. Otherwise, the iteration will continue
function deepCopy (obj, cache = []) {
if (obj === null || typeofobj ! = ='object') {
return obj
}
const item = cache.filter(item= > item.original === obj)[0]
if (item) return item.copy
let copy = Array.isArray(obj) ? [] : {}
cache.push({
original: obj,
copy
})
Object.keys(obj).forEach(key= > {
copy[key] = deepCopy(obj[key], cache)
})
return copy
}
deepCopy($obj)
/* {a: null b: undefined C: {a: 1, d: {... Date:}} {} e: {} f: {} func: ƒ () symbol: symbol ()}Copy the code
This solves the circular reference problem perfectly, but there are still several minor problems, all of which fall into the same category
The third edition addresses special values
For the final several object processing, you can determine the type, a new return can be
function deepCopy (obj, cache = []) {
if (obj === null || typeofobj ! = ='object') {
return obj
}
if (Object.prototype.toString.call(obj) === '[object Date]') return new Date(obj)
if (Object.prototype.toString.call(obj) === '[object RegExp]') return new RegExp(obj)
if (Object.prototype.toString.call(obj) === '[object Error]') return new Error(obj)
const item = cache.filter(item= > item.original === obj)[0]
if (item) return item.copy
let copy = Array.isArray(obj) ? [] : {}
cache.push({
original: obj,
copy
})
Object.keys(obj).forEach(key= > {
copy[key] = deepCopy(obj[key], cache)
})
return copy
}
deepCopy($obj)
/* {a: null b: undefined C: {a: 1, d: {... Date: Apr 10 2020 20:06:08 GMT+0800 {} e: /regexp/ f: Error: Error: error at deepCopy (
:8:74) at
:19:21 at Array.forEach (
) at deepCopy (
:18:22) ƒ () symbol: symbol ()} */
Copy the code
In addition, Promise, Set, Map, WeakSet, WeakMap can also be treated in the same wayCopy the code
The fourth version addresses the same function references
At this point, the basic functionality seems to be achieved, but there is a problem that functions refer to the same memory address, for which most of the web is either directly returned or returned as an object, including LoDash
const isFunc = typeof value == 'function'
if(isFunc || ! cloneableTags[tag]) {return object ? value : {}
}
Copy the code
The question of how to end this problem requires the eval function, which is no longer recommended, but it can still solve the problem. There are two types of functions: normal functions and arrow functions. If there is a prototype attribute, it belongs to normal functions; if there is no arrow function, it belongs to normal functions.
Ordinary functions are function declarations and cannot use eval directly. They need to be wrapped in parentheses to form function expressions. Arrow functions are function expressions themselves
// lmran
function copyFunction(func) {
let fnStr = func.toString()
return func.prototype ? eval(` (${fnStr}) `) : eval(fnStr)
}
function deepCopy (obj, cache = []) {
if (typeof obj === 'function') {
return copyFunction(obj)
}
if (obj === null || typeofobj ! = ='object') {
return obj
}
if (Object.prototype.toString.call(obj) === '[object Date]') return new Date(obj)
if (Object.prototype.toString.call(obj) === '[object RegExp]') return new RegExp(obj)
if (Object.prototype.toString.call(obj) === '[object Error]') return new Error(obj)
const item = cache.filter(item= > item.original === obj)[0]
if (item) return item.copy
let copy = Array.isArray(obj) ? [] : {}
cache.push({
original: obj,
copy
})
Object.keys(obj).forEach(key= > {
copy[key] = deepCopy(obj[key], cache)
})
return copy
}
deepCopy($obj).func === $obj.func // false
Copy the code
conclusion
At this point, deep and shallow copying is complete, but there is no end to learning. We can also consider using Proxy enhancement for deep copy performance, by intercepting sets and get, and of course Object.defineProperty(). If you are interested, you can check out this article, which is very detailed. Headline Interviewer: Do you know how to implement a high performance version of deep copy?
disambiguation
In view of the same problem of function reference raised by @brota, this paper has been fixed, but if the function is a built-in function code still has a problem, the solution is provided, if the function is a built-in function directly return. The built-in function itself has a copy, so there is no problem with the same reference address
function copyFunction(func) {
let fnStr = func.toString()
if (fnStr === `function ${func.name}() { [native code] }`) {
return func
}
return func.prototype ? eval(` (${fnStr}) `) : eval(fnStr)
}
Copy the code
Has not yet realized the function: closure of the processing, has not found a way to deal with, know you can leave a comment belowCopy the code
If you have any questions in this article, please kindly point them out. Thank you!!
reference
- Use of the MDN eval function
- How to write a deep copy that will impress your interviewer?
- Headline Interviewer: Do you know how to implement a high-performance version of deep copy?