This time, thoroughly grasp the depth of JavaScript copy

Hanpeng_ Chen 2021-02-23 16:09:11
time thoroughly grasp depth javascript


About copying , It's also a classic interview question in the front-end interview , In our daily development, we often encounter scenes that need to use deep copy or shallow copy . Next, let's go through this article , To master thoroughly JavaScript Deep and light copies of .

data type

Before we start talking about deep and shallow copy , We need to know first JavaScript Data type of , Mainly as shown in the figure below 8 Kind of :

Object Is a reference type , other 7 It is the basic type .

JavaScript All the data types of will be put in different memory after initialization , Therefore, the above data types can be roughly divided into two categories for storage :

  • The underlying type is stored in stack memory , When quoted or copied , Will create an exactly equal variable
  • Reference types are stored in heap memory , What's stored is the address , Multiple references point to the same address , There will be a “ share ” The concept of .

Let's take a look at the shallow copy first .

Welcome to the official account : Front end geek Technology (FrontGeek), We learn together and make progress together .

The principle and implementation of shallow copy

Definition of light copy :

Create an object to accept the value of the object to be copied or referenced again . If the object property is the basic data type , What is copied is the value of the basic type to the new object ; If the property is a reference data type , The copied address is the address in memory , If one of the objects changes the address in memory , It could affect another object .

Let's see JavaScript What are the ways to achieve shallow copy in the .

Object.assign

object.assign yes ES6 in object One way , This method can be used for JS Object merging and so on , One of the purposes is to make shallow copies . The first parameter of this method is the target object of the copy , The next parameter is the source object of the copy ( It can also be multiple sources ).

object.assign The grammar of is :Object.assign(target, ...sources)

object.assign The sample code of is as follows :

let target = {}
let source = {
a: {
b: 1
}
}
Object.assign(target, source)
source.a.b = 10;
console.log(source); // { a: { b: 10 } }; 
console.log(target); // { a: { b: 10 } };
 Copy code 

From the above code we can see , First, through Object.assign take source copy to target In the object , take source Object b Attribute from 1 It is amended as follows 10. As can be seen from the execution results target and source Of the two objects b All attributes change to 10 了 , prove Object.assign For the time being, the copy effect we wanted .

Use Object.assign Method to achieve shallow copy, there are several points to note :

  • Object.assign Method does not copy the inherited properties of the object
  • Object.assign Method does not copy the object's non enumerable properties
  • You can copy Symbol Properties of type
let obj1 = { a: { b: 1 }, sym: Symbol(1) };
Object.defineProperty(obj1, "innumerable", {
value: " Cannot enumerate properties ",
enumerable: false,
});
let obj2 = {};
Object.assign(obj2, obj1);
obj1.a.b = 10;
console.log("obj1", obj1); // obj1 { a: { b: 10 }, sym: Symbol(1) }
console.log("obj2", obj2); // obj1 { a: { b: 10 }, sym: Symbol(1) }
 Copy code 

As you can see from the sample code above , utilize object.assign It can also be copied Symbol Object of type , But if you get to the second layer of the object's properties obj1.a.b When it's here , The change of the value of the former will also affect the value of the second layer attribute of the latter , It shows that there is still the problem of accessing common heap memory , In other words, this method can not be further copied , And just finished the shallow copy function .

Extension operator methods

We can also use it JS The extension operator of , In the construction of objects at the same time to complete the shallow copy function .

let obj = {a: 1, b: {c: 10}}
let obj2 = {...obj}
obj2.a = 10
console.log(obj);
console.log(obj2);
let arr = [1, 3, 4, 5, [10, 20]]
let arr2 = [...arr]
console.log(arr2)
 Copy code 

Extension operator and object.assign With the same flaws , That is to say, the function of shallow copy is almost the same , But if the properties are all values of the base type , It's more convenient to use extension operators for shallow copy .

concat Copy an array

Array of concat The method is also shallow copy , So when you join an array with a reference type , You need to pay attention to modifying the attributes of the elements in the original array , Because it will affect the array connected after copying .

let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1]= 10;
console.log(arr);
console.log(newArr);
 Copy code 

slice Copy an array

slice The method also has limitations , Because it's only for array types .

slice Method returns a new array object , This object determines the start and end time of the original array interception by the first two parameters of the method , Will not affect and change the original array .

let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr); //[ 1, 2, { val: 1000 } ]
 Copy code 

As you can see from the code above , This is the limit of shallow copy —— It can only copy one layer of objects . If there is nesting of objects , So there's nothing a shallow copy can do . So deep copy was created to solve this problem , It can solve the problem of multi-layer object nesting , Complete copy . Later in this lecture, I will introduce the content related to deep copy .

Make a shallow copy by hand

In addition to the methods mentioned above , We can also write a shallow copy method , Ideas as follows :

  • Make a basic copy of the basic data type ;
  • Open up a new store for reference types , And copy a layer of object properties .
const shallowCopy = (target) => {
if (typeof target === "object" && target !== null) {
const copyTarget = Array.isArray(target) ? [] : {};
for (let key in target) {
if (target.hasOwnProperty(key)) {
copyTarget[key] = target[key];
}
}
return copyTarget;
} else {
return target;
}
};
 Copy code 

The principle and implementation of deep copy

Shallow copy just creates a new object , Copy the value of the basic type of the original object , The reference data type only copies one layer of properties , No matter how deep it is, it still can't be copied . Deep copies are different , For complex reference data types , It completely opens up a memory address in heap memory , And copy the original object completely .

These two objects are independent of each other 、 Unaffected , Complete separation of memory . in general , The principle of deep copy can be summarized as follows :

A complete copy of an object from memory and a copy to the target object , And from the heap memory to open up a new space to store new objects , And the modification of the new object does not completely change the source object , There is a real separation between the two .

After knowing the principle of deep copy , Let's see what deep copy methods are available :

The beggar version :JSON.stringfy

JSON.stringfy() Method is the most common and simplest deep copy method in development , It's basically serializing an object to JSON String , And convert the content of the object to a string , Last but not least JSON.parse() Methods will JSON String to generate a new object .

let obj = {a: 1, b: [1, 2, 3]};
let str = JSON.stringify(obj);
let newObj = JSON.parse(str);
obj.a = 2;
obj.b.push(4)
console.log(obj)
console.log(newObj)
 Copy code 

As you can see from the code above , adopt JSON.stringfy Can initially achieve a deep copy of the object , By changing obj Of a attribute , In fact, we can see that newObj This object is not affected either .

Let's take a look at the following uses JSON.stringify Method for deep copy :

function Obj() {
this.func = function () { alert(1) };
this.obj = {a:1};
this.arr = [1,2,3];
this.und = undefined;
this.reg = /123/;
this.date = new Date(0);
this.NaN = NaN;
this.infinity = Infinity;
this.sym = Symbol(1);
}
let obj1 = new Obj();
Object.defineProperty(obj1,'innumerable',{
enumerable:false,
value:'innumerable'
});
console.log('obj1',obj1);
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
console.log('obj2',obj2);
 Copy code 

From the execution results above ,JSON.stringify The way to achieve deep copy is not perfect , Let's take a look JSON.stringify The problems in implementing deep copy :

  • If the copied object has a function 、undefined、symbol There are several types , after JSON.stringify The key value pair disappears in the serialized string ;
  • Copy Date The reference type becomes a string
  • Can't copy properties that can't be enumerated
  • Can't copy prototype chain of object
  • Copy RegExp Reference type becomes empty
  • Object contains NaN、Infinity、-Infinity,JSON The result of serialization would be null
  • Can't copy circular application of object , That is to say, the object forms a ring , for example obj[key]=obj

use JSON.stringify Method to implement deep copy object , Although so far there are many functions that can't be realized , But this method is enough to meet the daily development needs , And it's the easiest and quickest .

If the deep copy method needs to support Function、Symbol And so on , We can only implement a deep copy method by ourselves .

Handwriting for deep copy ( Basic Edition )

Next, we implement a deep copy method ourselves , Ideas as follows :

adopt for in Traverse the property value of the incoming parameter , If the value refers to the type, the method is called recursively again , If so, the underlying type is copied directly .

function deepCopy(target) {
let copyTarget = Array.isArray(target) ? [] : {};
for (let key in target) {
if (typeof target[key] === "object") {
copyTarget[key] = deepCopy(target[key]);
} else {
copyTarget[key] = target[key];
}
}
return copyTarget;
}
let obj1 = {
a: {
b: 1,
},
c: 10,
};
let obj2 = deepCopy(obj1);
obj1.a.b = 100;
obj1.c = 22
console.log(obj2);
 Copy code 

Above, we implemented deep copy recursively , But there are still some problems to be solved :

  • Cannot copy non enumerable properties and Symbol type ;
  • Only the values of common reference types are recursively copied , But for the Array、Date、RegExp、Error、Function These reference types don't copy properly .
  • The properties of the object are looped , That is, circular reference is not solved .

Next, let's improve the deep copy method :

Improved deep copy method

In view of the above problems , Let's see what the solution is :

  • For enumerable properties and Symbol type , have access to Reflect.ownKeys Method ;
  • Parameter is Date、RegExp Type , Directly generate a new instance and return ;
  • utilize Object Of getOwnPropertyDescriptors Method to get all the properties of an object , And the corresponding characteristics , By the way Object.create() Method creates a new object , And inherits the chain of prototypes passed in to the original object ;
  • utilize WeakMap Type as Hash surface , because WeakMap Weak reference type , Can effectively prevent memory leakage , It helps to detect circular references , If there is a cycle , Then the reference returns WeakMap Stored values .

According to the above , Let's rewrite the deep copy method :

function deepCopy(obj, hash = new WeakMap()) {
// The date object directly returns a new date object 
if (obj.constructor === Date) return new Date(obj);
// If it is a regular object, return a new regular object directly 
if (obj.constructor === RegExp) return new RegExp(obj);
// If a loop reference is made, use WeakMap To solve 
if (hash.has(obj)) return hash.get(obj);
// Traverses the properties of all keys in the incoming parameter 
let allDesc = Object.getOwnPropertyDescriptor(obj);
// Inherit the prototype chain 
let copyObj = Object.create(Object.getPrototypeOf(obj), allDesc);
hash.set(obj, copyObj)
for (let key of Reflect.ownKeys(obj)) {
copyObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepCopy(obj[key], hash) : obj[key]
}
return copyObj
}
function isComplexDataType(obj) {
return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
}
// Here's the validation code 
let obj = {
num: 0,
str: '',
boolean: true,
unf: undefined,
nul: null,
obj: { name: ' I'm an object ', id: 1 },
arr: [0, 1, 2],
func: function () { console.log(' I'm a function ') },
date: new Date(0),
reg: new RegExp('/ I'm a regular /ig'),
[Symbol('1')]: 1,
};
Object.defineProperty(obj, 'innumerable', {
enumerable: false, value: ' Cannot enumerate properties ' }
);
obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop = obj // Set up loop Properties referenced in a loop 
let cloneObj = deepCopy(obj)
cloneObj.arr.push(4)
console.log('obj', obj)
console.log('cloneObj', cloneObj)
 Copy code 

Performance issues

Although using deep copy can completely clone a new object , No side effects , But because the deep copy method uses recursion , Performance is not as good as shallow copy , In the actual development process , We should choose according to the actual situation .

If you think this is going to help you :

1、 give the thumbs-up Support me , So that more people can see the content

2、 Official account : Front end geek Technology (FrontGeek), We learn together and make progress together .

版权声明
本文为[Hanpeng_ Chen]所创,转载请带上原文链接,感谢
https://javamana.com/2021/02/20210223155148572K.html

  1. Deep understanding of Web protocol (3): http 2
  2. Tencent IEG open source AI SDK: automatic testing of chicken eating and MoBa games
  3. Mysql Command
  4. Docker dockerfile create image
  5. Redis Cluster
  6. 死磕Spring之IoC篇 - 文章导读
  7. Deep understanding of Web protocol (3): http 2
  8. JavaScript BOM operation
  9. JavaScript BOM operation
  10. 死磕Spring之IoC篇 - 文章导读
  11. k8s node 操作与维护
  12. k8s 证书更新
  13. 【Java面试题第三期】JVM中哪些地方会出现内存溢出?出现的原因是什么?
  14. HashMap连环问你能答出几道?
  15. k8s-cronjob
  16. k8s-cert
  17. 头条面试官:说说Kafka的消费者提交方式,怎么实现的
  18. 什么是HTTPS以及如何实施HTTPS?
  19. Spring: an introduction to IOC
  20. Spring: an introduction to IOC
  21. Operation and maintenance of k8s node
  22. K8s certificate update
  23. vue使用sdk进行七牛上传
  24. k8s-dns
  25. JavaScript 邮箱验证 - 正则验证
  26. k8s-dashboard
  27. HashMap连环问你能答出几道?
  28. Where does memory overflow occur in the JVM? What are the reasons for this?
  29. How many questions can you answer?
  30. k8s-cronjob
  31. spring注解--Transactional
  32. k8s-cert
  33. Will the Spring Festival holiday be extended to February 27 in 2021? Here comes the response
  34. Headline Interviewer: talk about Kafka's consumer submission method, how to achieve it
  35. 【k8s集群】搭建步骤
  36. k8s-kubeadm
  37. k8s-etcd
  38. What is HTTPS and how to implement it?
  39. Java中使用HashMap改进查找性能
  40. maven发布jar包运行时找不到类问题
  41. J2EE
  42. Vue uses SDK to upload seven cows
  43. k8s-dns
  44. JavaScript mailbox verification - regular verification
  45. k8s-dashboard
  46. How many questions can you answer?
  47. Spring annotation -- transactional
  48. [k8s cluster] construction steps
  49. k8s-kubeadm
  50. k8s-etcd
  51. Using HashMap to improve search performance in Java
  52. There is no class problem when Maven publishes jar package
  53. JavaScriptBOM操作
  54. J2EE
  55. k8s-prometheus-memory
  56. k8s-prometheus disk
  57. k8s-prometheus
  58. JavaScript BOM operation
  59. k8s-prometheus-memory
  60. k8s-prometheus disk