The previous section on what constitutes JavaScript describes that the data types of JavaScript are divided into basic types and reference types, and the basis for distinguishing the two is that the basic type is "light", it exists in the stack memory, and the reference type is heavier, it exists in heap memory. Therefore, when the basic type is copied, it can be copied directly. When the reference type is copied, it is not the object (the reference type has and only one data type - object), but the address of the object in memory.
In everything is an object , we have clearly expressed a point of view, in the JavaScript world, except for undefined, null, everything is an object. One of the things that objects frequently use during use is assignment copying, and when you copy something wrong, things can go badly. So our section is born to explain the copy in the object
text
First of all, JavaScript does not have immutable data structures. Immutable data structures are necessary in functional programming.
The benefit of mutable is to save memory or do something with mutability, but in complex development its side effects are far greater than the benefits, so there are shallow and deep copies
The author has a few self-answered explanations here
Why is the object a copy address?
- For performance (saving memory), imagine that if each object is a copy value, then the object is large/too many, and the memory occupied will increase geometrically
How to copy the value of an object
- Object.assign
- spread operator
- slice (array method)
- concat (array method)
- JSON.stringify
These five methods can copy the value of the object, while the first four are shallow copies, and JSON.stringify is a deep copy
What is a shallow copy? What is a deep copy again?
A shallow copy creates a new object that replicates the original object's property values
- The attribute is the basic type, the value of the basic type is copied, and the modified content does not affect
- The attribute is a reference type, the copy is the memory address, and the modified content affects each other
- Deep copy: The entire object is copied to another memory, and the modified content does not affect each other
To put it bluntly, a shallow copy only copies one layer, and a deep copy directly copies the object
Why should there be a shallow copy instead of a direct deep copy. Of course, this question was also raised by netizens - what is the role of JS's shallow copy? , Although the author did not find relevant information, I suspect that due to performance, shallow copy can cope with many scenarios, and deep copy is not necessary. In design, let developers use less, and virtually improve the development experience
Object.assign
The Object.assign() method can copy any number of enumerable properties of the original object to the target object, and then return the target object
It copies references to properties of the object, not the object itself
Is a new method of the Object object in ES6
parameter:
target: target object
sources: any number of original objects.
Return value: the target object will be returned
Applicable object: Object
Case number one:
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = Object.assign({}, obj1);
obj2.b = 100;
console.log(obj1); // {a: 10, b: 20, c: 30}
console.log(obj2); // {a: 10, b: 100, c: 30}
Case 2:
var obj = { a: { a: 'hello', b: 21 } };
var initialObj = Object.assign({}, obj);
initialObj.a.a = 'changed';
console.log(obj.a.a); // "change"
It can be seen that Object can only copy the first layer of objects. If it is copied to a deeper layer, there will be a problem. So Object.assign is a shallow copy
spread operator (...)
Spread operator, which can expand array expressions or strings at the syntax level during function call/array construction; and expand object expressions by key-value when constructing literal objects
Granted, we all know that the spread operator is not meant for copying. But it is understandable, shallow copy is also one of the function points of the spread operator
Applicable objects: Object/Array
Case 1: One-dimensional array
var arr = [1, 2, 3];
var arr2 = [...arr];
arr2.push(4);
// arr2 [1, 2, 3, 4]
// arr1 不受影响
Case 2: Multidimensional Arrays
var a = [
[1, 2],
[3, 4],
[5, 6],
];
var b = [...a];
b.shift().shift();
// b [[3, 4], [5, 6]]
// a [[2], [3, 4], [5, 6]]
Spread operator is also a shallow copy
slice
The slice() method returns a new array object that is a shallow copy of the original array determined by begin
and end
(including begin
excluding --- end
). The original array will not be changed
Applicable object: Array
Case:
const family = [
'father',
'mother',
'brother',
['sister0', 'sister1', 'sister2'],
];
const copyFamily = family.slice();
copyFamily[0] = 'father1';
copyFamily[3][1] = 'brother1';
console.log(family); // ['father', 'mother', 'brother', ['sister0' , 'brother1', 'sister2']]
console.log(copyFamily); // ['father1', 'mother', 'brother', ['sister0' , 'brother1', 'sister2']]
// 复制一层,第二层开始引用
In the above case, the slice can only copy one layer, the second layer is to copy the reference address, and the slice is also a shallow copy
concat
The concat() method is used to combine two or more arrays. This method does not mutate the existing array, but returns a new array
Applicable object: Array
const array1 = ['a', 'b', ['c0', 'c1', 'c2']];
const array2 = array1.concat();
array2[1] = 'B';
array2[2][1] = 'C1';
console.log(array1); // ['a', 'b', ['c0', 'C1', 'c2']]
console.log(array2); // ['a', 'B', ['c0', 'C1', 'c2']]
// 复制一层,第二层开始引用
concat is the same as slice, both are shallow copies of arrays
How to implement shallow copy
In simple terms, a shallow copy only copies the properties of one layer of objects
The role of hasOwnProperty is to determine whether the object itself has the specified property
function shallowClone(source) {
if (typeof target === 'object' && target !== null) {
var target = Array.isArry(source) ? [] : {};
for (let prop in source) {
if (source.hasOwnProperty(prop)) {
target[prop] = source[prop];
}
}
return target;
} else {
return source;
}
}
To sum up, there are 4 kinds of shallow copy in JavaScript. There are slice and concat for shallow copy of array, Object.assign() for object, and spread operator (...) for array and object.
The principle of deep copy
Shallow copy just creates a new object and copies the value of the basic type of the original object, while the reference type only copies one layer of properties, and the deeper one cannot be copied. The deep copy is different. It will open up a memory address in the heap memory and completely copy the original object.
Deep copy is to copy an object completely from memory to the target object, and open up a new space in heap memory to store the new object, and the modification of the new object will not change the meta object. separation
Simple induction: deep copy is to recursively copy the properties of objects in all levels
JSON.stringify
var arr = [1, 2, 3, 4, { value: 5 }];
var arr1 = JSON.parse(JSON.stringify(arr));
arr[4].value = 6;
console.log(arr1); //[1, 2, 3, 4, { value: 5 }]
var obj = {
name: "johan",
address: {city: "shanghai"}
}
var obj1 = JSON.parse(JSON.stringify(obj));
obj.address.city = "beijing";
console.log(obj1); //{name: "johan", address:{city: "shanghai"}
Although JSON.stringify can achieve deep copying of arrays and objects, it has several pitfalls
- It cannot clone special objects like functions, RegExp, etc.
- It will discard the constructor of the object, all constructors will point to Object
- The object has a circular reference, an error will be reported
Let's test these pits,
// 构造函数
function Person(name) {
this.name = name;
}
const Elaine = new Person('elaine');
// 函数
function say() {
console.log('hi');
}
const oldObj = {
a: say,
b: new Array(1),
c: new RegExp('ab+c', 'i'),
d: Elaine,
};
const newObj = JSON.parse(JSON.stringify(oldObj));
// 无法复制函数
console.log(newObj.a, oldObj.a); // undefined [Function: say]
// 稀疏数组复制错误
console.log(newObj.b[0], oldObj.b[0]); // null undefined
// 无法复制正则对象
console.log(newObj.c, oldObj.c); // {} /ab+c/i
// 构造函数指向错误
console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: Object] [Function: person]
We can see in pairs of functions, regular objects. An accident will occur when cloning objects such as sparse arrays, and an error will occur when the constructor points to
const oldObj = {};
oldObj.a = oldObj;
const newObj = JSON.parse(JSON.stringify(oldObj));
console.log(newObj.a, oldObj.a); // TypeError: Converting circular structure to JSON
Circular reference to object throws error
JSON.stringify deep copy can solve most scenarios in reality, but its flaws also make it a frequent visitor in interviews. Now we challenge handwritten deep copy
handwritten deep copy
The key to deep copy is recursion + deep copy
We first implement a deep copy for arrays and objects
function deepClone(source) {
// 针对基本数据类型
if (typeof source !== 'object' || source === null ) {
return source
}
// 判断它是数组还是对象
// 或者 let target = source instanceof Array ? [] : {}
let target = Array.isArray(source) ? [] : {}
// 循环遍历复制每个属性
for (let prop in source) {
// 自有属性才做拷贝
if (source.hasOwnProperty(prop)) {
// 判断自有属性是否是对象
target[prop] = typeof source[prop] === 'object' ? deepClone(source[prop]) : source[prop]
}
}
return target
}
The above is a simple deep copy, which is similar to the deep copy effect of JSON.stringify. They also have the disadvantages of:
- Executing this method on objects that contain circular references (objects refer to each other, forming an infinite loop) will throw an error
- Attributes with the Symbol type as the attribute value will be ignored
- Lack of compatibility with other built-in constructors such as Function, RegExp, Date, Set, Map
We use WeakMap to solve circular references, if you want other data types, add
Here's why we use WeakMap to solve circular references and how it differs from Map
To solve the circular reference problem, you can open up an additional storage space to store the corresponding relationship between the current object and the copied object. When copying, first look for it in the space, find it and return it directly, if not, copy it normally
And this kind of data structure can use map, WeakMap. The difference between the two is
- A WeakMap object is a collection of key/value pairs, where the keys are weak references. Its keys must be objects, and the values can be arbitrary. Map keys can be arbitrary, including functions, objects, or any primitive type
- WeakMaps are weak references and can be garbage collected. Map keys are bound to memory
- Map can be traversed, WeakMap cannot be traversed
Simply put, because WeakMap is a weak reference, garbage collection can proceed normally when no other references exist.
function deepClone(source, storage = new WeakMap()) {
// 针对基本数据类型
if (typeof source !== 'object' || source === null) {
return source
}
// 是否是日期
if (source.constructor === Date) {
return new Date(source)
}
// 是否是正则
if (source.constructor === RegExp) {
return new RegExp(source)
}
// 是否是数组
let target = source instanceof Array ? [] : {}
// 循环引用 返回存储的引用数据
if (storage.has(source)) return storage.get(source)
// 开辟存储空间设置临时存储值
storage.set(source, target)
// 是否包含 Symbol 类型
let isSymbol = Object.getOwnPropertySymbols(source)
// 包含 Symbol 类型
if (isSymbol.length) {
isSymbol.forEach((item) => {
if (typeof source[item] === 'object') {
target[item] = deepClone(source[item], storage);
return
}
target[item] = source[item]
})
}
// 不包含 Symbol
for(let key in source) {
if (source.hasOwnProperty(key)) {
target[key] = typeof source[key] === 'object' ? deepClone(sourcep[key], storage) : source[key]
}
}
return target;
}
This deep copy of the author is definitely not the most complete, and the deep copy written by non-big guys that amazes the interviewer is comparable. The author can only say that it provides the idea of a deep copy
Summarize
Deep copying is a must in the front-end interview. If he asks you how to write by hand, if you only write JSON.parse(JSON.stringify(source)), you will definitely be unqualified. Writing hasOwnProperty can only stand on the sidelines, and if you solve the problems of circular reference, Symbol, copying of various data types, etc., it can show that you understand deep copying
References
- Deep and shallow copies of arrays using slice and concat
- How to write a deep copy that will surprise the interviewer
- Walking in the courtyard and chatting on the front-end - an article to find out the depth of ES copy
- How to write a deep copy that will surprise the interviewer?
series of articles
- Deep understanding of JavaScript - the beginning
- Deep understanding of JavaScript - what is JavaScript
- Deep understanding of JavaScript - what JavaScript consists of
- Deep understanding of JavaScript - everything is an object
- Deep understanding of JavaScript-Object (object)
- In-depth understanding of what JavaScript-new does
- Deep understanding of JavaScript-Object.create
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。