The relationship between variables and memory
In JS, memory space is generally divided into stack, heap:
- Stack general storage rules, occupy a small space and the size of the basic fixed data, so
Basic data types
General basic data types are stored in stacks - The heap generally stores irregular, occupies large space and the size of the data is not fixed, so the general
Reference data type
Stored in the heap
Reference data types typically store a reference address on the stack. Therefore, when accessing a reference data type, we first access the reference address stored on the stack, and then access the actual data through that address
const a = 1
const b = {
b1:1.b2:2
}
Copy the code
Why do I need to copy data?
let a = 1
let b = {
b1:1.b2:2
}
const c = a
const d =b
a = 2
b.b1=1.1
Copy the code
As you can see, d creates a new space in the stack, but it points to the same heap pointer as B, so when you modify the heap, d also changes.
Most of the time, however, we need them to be independent of each other, just like the basic data types. In this case, we need to make a copy of the heap data and point to the new heap data
Consider: why is data required to be a function when writing vue templates?
// test1
var a = {
test1:1.test2:2
}
var b = a
var c = a
console.log(b === c) // true
// test2
var a = function (){
return {
test1:1.test2:2}}var b = a()
var c = a()
console.log(b === c) // false
Copy the code
Can be found, when we create objects within the function returns the object way to create, to ensure that every assignment is a fresh each other objects, so the data in the vue template to create data by means of function, can guarantee multiple local reference the same template, data will not affect each other between each other
A copy of the reference type
Generally when we talk about copy, we think about shallow copy and deep copy;
Shallow copy
If there is a reference type in the second layer, then there is still a problem.- I explicitly know that the object I want to copy is a simple, one-layer data
- Even though the object is more complex, shallow copy has a slight performance boost when it is clear that only the first layer of simple variable data will be changed and there is no second layer of variable data
Deep copy
Is a complete copy of an object, no matter how deep, how complex, generally for the companyUtil method
Deep copy is used
Shallow copy implementation
Method 1: Object.assign
const obj = {a:1.b:2};
const cloneObj = Object.assign({}, obj);
Copy the code
Note: Object.assign copies only the source Object’s own enumerable attributes, using the source Object’s [[Get]] and the target Object’s [[Set]]. Therefore, if the Object to be copied contains inherited and non-enumerable attributes, these attributes will not be copied
Method two: by deconstructing the method of assignment
const obj = {a:1.b:2};
constcloneObj = {... obj};Copy the code
Tip: The expansion syntax is identical to the behavior of object.assign ()
Deep copy implementation
Method 1: json.parse
Json.stringify converts the data to a JSON string, stores it on the stack, and then converts it back to objects via json.parse and stores it in the heap. A copy of the object is cleverly recreated by converting it to a string
const obj = {
a: {a1:1.a2:2},
b: {b1:1.b2:2}};const cloneObj = JSON.parse(JSON.stringify(obj));
Copy the code
For such a simple deep copy, there are limitations:
- For such as
Map
.Set
.RegExp
.Date
.ArrayBuffer
和Other built-in types
Can be lost or changed during serialization.
var obj = {
a: function() {}, / / lost
b: new RegExp(/ab+c/.'i'),
c: 123.d: new Set([1.2.3])}console.log(JSON.parse(JSON.stringify(obj)))
/ / {
// b:{},
// c:123,
// d:{}
// }
Copy the code
- An error is reported if there is a circular reference relationship inside the object
var obj = {
a:1
}
var obj2 = {
b: obj
}
obj.c = obj2
console.log(JSON.parse(JSON.stringify(obj)))
// Uncaught TypeError: Converting circular structure to JSON
Copy the code
- If the object is too deep, it will
Stack overflow
var obj = {}, cur = obj;
for (let i = 0; i < 10000; i++) {
cur.next = {}
cur = cur.next
}
JSON.parse(JSON.stringify(obj))
// RangeError: Maximum call stack size exceeded
Copy the code
Therefore, if you know the structure and complexity of the object to be copied, you can consider whether to use this method. Extension: Implement the json.stringify function
Method two: recursion
// This is an interview
function deepClone(obj){
if (obj instanceof RegExp) return new RegExp(obj)
if (obj instanceof Date) return new Date(obj)
if (typeofobj ! = ="object" || obj === null) return obj;
let newObj=Array.isArray(obj)? [] : {}for(var attr in obj){
if (Object.prototype.hasOwnProperty.call(obj, attr)) {
newObj[attr]=deepClone(obj[attr])
}
}
return newObj
}
Copy the code
However, the deep copy of this recursive version still has the problem of method 1:
- The presence of circular references will burst the stack
RangeError: Maximum call stack size exceeded
- Objects that are too deep in the hierarchy will also burst the stack
RangeError: Maximum call stack size exceeded
Solve recursion is a circular reference problem
The circular reference problem can be solved by storing objects that have already been traversed in a Map or other way, then determining whether the Map has been stored in each recursion, and returning the stored objects if so
function deepClone(obj, map = new Map(a)){
if (obj instanceof RegExp) return new RegExp(obj)
if (obj instanceof Date) return new Date(obj)
if (typeofobj ! = ="object" || obj === null) return obj;
let newObj=Array.isArray(obj)? [] : {}if (map.get(obj)) {
return obj;
}
map.set(obj, newObj);
for(var attr in obj){
if (Object.prototype.hasOwnProperty.call(obj, attr)) {
newObj[attr]=deepClone(obj[attr], map)
}
}
return newObj
}
Copy the code
Fixed a problem with too deep burst stack
It is theoretically possible to solve this problem recursively using tail calls. Tail-call optimization: The ES6 specification states that as long as tail-calls are implemented, there will be no stack overflow errors in recursion. The reality is that many browsers do not implement this specification, and stack overflow errors are still reported, and tail calls are restricted only in strict mode. The V8 engine used to do this, but later removed call optimization because tail-recursive optimization would destroy the function’s call stack information. TC39 is also looking for new syntax to explicitly indicate the need to use tail-recursive optimization.
Solve the problem of level too deep blasting stack 2: DFS | | BFS
In theory, all recursion can be done iteratively, and iterating doesn’t cause stack overflow, but it’s just not as easy to do! In the case of deep copy, we can implement it by stack + iteration: depth-first traversal (DFS) or breadth-first traversal (BFS). The implementation principle is similar. We use BFS as an example: source
function getEmpty(o){
if(Object.prototype.toString.call(o) === '[object Object]') {return {};
}
if(Object.prototype.toString.call(o) === '[object Array]') {return [];
}
return o;
}
function deepCloneBFS(origin) {
let queue = []; // Stack destruct
let map = new Map(a);// Record the occurrence of the object, used to process the ring
let target = getEmpty(origin);
if(target ! == origin){ queue.push([origin, target]); map.set(origin, target); }while(queue.length){
let [ori, tar] = queue.shift();
for(let key in ori){
// Handle the loop
if(map.get(ori[key])){
tar[key] = map.get(ori[key]);
continue;
}
tar[key] = getEmpty(ori[key]);
if(tar[key] ! == ori[key]){ queue.push([ori[key], tar[key]]); map.set(ori[key], tar[key]); }}}return target;
}
Copy the code
Structured cloning algorithm
MDNThe HTML5 specification defines the algorithm for copying complex JavaScript objects, can avoid infinite traversal loops, support more data types, however, should only be built into the API to achieve this function, so if you need to use this function will need to use the built-in API, indirect cloning! Channel Messaging API Channel Messaging interface implementation compatibility is relatively good.
Node Serialization API (v8.0.0 +)
Node provides a serialization API comparable to HTML structured cloning and is relatively simple to use
const buf = v8.serialize(obj);
const clone = v8.deserialize(buf);
console.log(clone);
Copy the code
Immerjs takes a different approach
Actually the above methods has a problem, that is, whether we use the data, will be a traversal of all data on, for huge data, the performance is not very good, so you can consider to use objects by means of Proxy agent way to copy, unused part, is still the same data. Immer is through this idea, with the minimum cost to implement JS immutable objects
The last
The best method of cloning depends on the environment used and the complexity of the object.
The starting address
reference
- Headline Interviewer: Do you know how to implement a high-performance version of deep copy?
- Deep-copying in JavaScript
- JavaScript deep copy performance analysis