Deep copy implementation
This is the third day of my participation in Gwen Challenge
Introduction to the
The assignment of an object type actually copies the address, resulting in changes to one side and changes to the other. The solution to this problem is to use deep/shallow copies
Shallow copies can use extension operators directly (…) Or object. assign
Deep copy does not have a ready-made function
Json.parse (json.stringify (data)) is commonly used in business development for most deep-copy scenarios
But interviews generally look at using recursion to implement a deep copy
This paper will introduce the implementation scheme of each boundary case in detail
Common boundary problems
A circular reference
function
regular
The date of
symbol
Multiple key values refer to the same object, keeping the copied properties the same
- .
1. Simple recursive implementation
Regardless of the boundary problem, elements only have value types, obj, arr
function deepClone(obj) {
if(! isObject(obj))return obj
if (Array.isArray(obj)) {
const newObj = []
for (const v of obj) {
newObj.push(isObject(v) ? deepClone(v) : v)
}
return newObj
}
if (isObject(obj)) {
const newObj = {}
Object.keys(obj).forEach(k= > {
const v = obj[k]
newObj[k] = isObject(v) ? deepClone(v) : v
})
return newObj
}
}
const a = {
name: 'xiaoming'.id: 123131.info: {
bd: '2020-01-01'.cards: [{
q: 'q'.w: [1.2.3].e: { c: 'c'}}}}]console.log(JSON.stringify(deepClone(a)));
Copy the code
2. Solve circular references
It is well known that using JSON for deep copy does not solve the circular reference of an object, and an error will be reported if it occurs
You can solve this problem by using hash tables to record existing objects
Deepclone has been tweaked slightly above
function deepClone(obj) {
const map = new WeakMap(a)const dp = (obj) = > {
if(! isObject(obj))return obj
// Resolve circular references
if (map.has(obj)) return map.get(obj)
map.set(obj, Array.isArray(obj) ? [] : {})
if (Array.isArray(obj)) {
const newObj = []
for (const v of obj) {
newObj.push(isObject(v) ? dp(v) : v)
}
return newObj
}
if (isObject(obj)) {
const newObj = {}
Object.keys(obj).forEach(k= > {
const v = obj[k]
newObj[k] = isObject(v) ? dp(v) : v
})
return newObj
}
}
return dp(obj)
}
const b = {}, c = {}
b.next = c
c.next = b
console.log(deepClone(b)); // { next: { next: {} } }
Copy the code
3. Preserve the properties referenced by the original object
- Store the copied object
- Clone objects are returned directly
function deepClone(obj) {
const map = new WeakMap(a)const dp = (obj) = > {
if(! isObject(obj))return obj
// Clone objects are returned directly
if (map.has(obj)) return map.get(obj)
// Resolve circular references
map.set(obj, Array.isArray(obj) ? [] : {})
if (Array.isArray(obj)) {
const newObj = []
for (const v of obj) {
newObj.push(isObject(v) ? dp(v) : v)
}
// Store the copied object
map.set(obj, newObj)
return newObj
}
if (isObject(obj)) {
const newObj = {}
Object.keys(obj).forEach(k= > {
const v = obj[k]
newObj[k] = isObject(v) ? dp(v) : v
})
// Store the copied object
map.set(obj, newObj)
return newObj
}
}
return dp(obj)
}
const obj = { a: 1 }
const t3 = { a: obj, d: obj, f: { g: obj } }
const tt3 = deepClone(t3)
console.log(tt3); // { a: { a: 1 }, d: { a: 1 }, f: { g: { a: 1 } } }
console.log(tt3.a === tt3.d); // true
console.log(tt3.a === tt3.f.g); // true
Copy the code
4. Copy Symbol
How to obtain the Symbol key of an object
There are several ways to get the key of an object
Reflect.ownKeys(target)
The: method returns an array of the target object’s own property keys (Contains regular and Symbol keys
)Object.getOwnPropertySymbols(target)
: returns the value of a given object itselfAll Symbol attributes
An array ofObject.getOwnPropertyNames(target)
: returns an attribute name containing all the properties of the specified object (Includes non-enumerable properties
butDoes not include the Symbol value as a name attribute
)Object.keys()
: Returns a value from a given objectSelf enumerable properties
An array of property names in the same order that is returned when the object is iterated through normally
To sum up
Reflect.ownKeys(target)
/ / equivalent to the
Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
Copy the code
Keys(targer); keys(targer); keys(Targer);
function deepClone(obj) {
const map = new WeakMap(a)const dp = (obj) = > {
if(! isObject(obj))return obj
// Clone objects are returned directly
if (map.has(obj)) return map.get(obj)
// Resolve circular references
map.set(obj, Array.isArray(obj) ? [] : {})
if (Array.isArray(obj)) {
const newObj = []
for (const v of obj) {
newObj.push(isObject(v) ? dp(v) : v)
}
// Store the copied object
map.set(obj, newObj)
return newObj
}
if (isObject(obj)) {
const newObj = {}
// Use reflect.ownkeys instead
Reflect.ownKeys(obj).forEach(k= > {
const v = obj[k]
newObj[k] = isObject(v) ? dp(v) : v
})
// Store the copied object
map.set(obj, newObj)
return newObj
}
}
return dp(obj)
}
const s1 = Symbol.for('s1')
const s2 = Symbol.for('s2')
const data = {
[s1]: {
name: 's1'.age: 19
},
[s2]: [1.2.'string', {
title: s1
}]
}
console.log(deepClone(data));
// { [Symbol(s1)]: { name: 's1', age: 19 },
// [Symbol(s2)]: [ 1, 2, 'string', { title: Symbol(s1) } ] }
Copy the code
5. Copy the special object Date/RegExp
For special objects, we can handle them in the following steps
- Gets the constructor of the object
- Checks if it is the specified special object
- Call the constructor to generate a new object
Instantiated objects can be obtained from.constructor
Let’s modify the clone method above
function deepClone(obj) {
const map = new WeakMap(a)const dp = (obj) = > {
if(! isObject(obj))return obj
// Clone objects are returned directly
if (map.has(obj)) return map.get(obj)
// Resolve circular references
map.set(obj, Array.isArray(obj) ? [] : {})
// Get the object's constructor
const fn = obj.constructor
// If it is regular
if (fn === RegExp) {
return new RegExp(obj)
}
// If it is a date
if (fn === Date) {
return new Date(obj.getTime())
}
if (Array.isArray(obj)) {
const newObj = []
for (const v of obj) {
newObj.push(isObject(v) ? dp(v) : v)
}
// Store the copied object
map.set(obj, newObj)
return newObj
}
if (isObject(obj)) {
const newObj = {}
// Use reflect.ownkeys instead
Reflect.ownKeys(obj).forEach(k= > {
const v = obj[k]
newObj[k] = isObject(v) ? dp(v) : v
})
// Store the copied object
map.set(obj, newObj)
return newObj
}
}
return dp(obj)
}
const data = {
today: new Date(),
reg: /^abc$/ig
}
console.log(deepClone(data)); // {today: 2020-09-0t08:45:26.907z, reg: /^ ABC $/gi}
Copy the code
Copy function
Function copy program on the Internet collected a variety of, all kinds of strange skills, the following to give you a list of ha ha
- Using the eval:
- Eval (fn.tostring ()) : Only arrow functions are supported
- New Function(‘ return ‘+fn.toString())(): Cannot clone a Function and its original scope
- Fn.bind () : The new function returned can no longer use bind to change the this reference
// I'll simply use.bind
function deepClone(obj) {
const map = new WeakMap(a)const dp = (obj) = > {
if(! isObject(obj))return obj
// Clone objects are returned directly
if (map.has(obj)) return map.get(obj)
// Resolve circular references
map.set(obj, Array.isArray(obj) ? [] : {})
// Get the object's constructor
const fn = obj.constructor
// If it is regular
if (fn === RegExp) {
return new RegExp(obj)
}
// If it is a date
if (fn === Date) {
return new Date(obj.getTime())
}
// if it is a function
if (fn === Function) {
return obj.bind({})
}
if (Array.isArray(obj)) {
const newObj = []
for (const v of obj) {
newObj.push(isObject(v) ? dp(v) : v)
}
// Store the copied object
map.set(obj, newObj)
return newObj
}
if (isObject(obj)) {
const newObj = {}
// Use reflect.ownkeys instead
Reflect.ownKeys(obj).forEach(k= > {
const v = obj[k]
newObj[k] = isObject(v) ? dp(v) : v
})
// Store the copied object
map.set(obj, newObj)
return newObj
}
}
return dp(obj)
}
const data = {
today: new Date(),
reg: /^abc$/ig,
fn1: (a, b) = > {
console.log(this.today);
console.log(a + b);
},
fn2: function (a, b) {
console.log(this.reg);
console.log(a + b); }}const newData = deepClone(data)
newData.fn1(1.2) // undefined 3
newData.fn1.call({ today: '666' }, 1.2) // undefined 3
newData.fn2(3.4) // /^abc$/gi 7
newData.fn2.call({ reg: 123 }, 3.4) / / 123 7
const fn2 = newData.fn2
fn2.call({ reg: 'fn2Call' }, 2.3) // fn2Call 5
const fn3 = fn2.bind({ reg: 'string' })
fn3(2.3) // string 5
Copy the code
You can taste this article in detail for more details
For the full implementation of deep copy you can look at Lodash.clonedeep, source code