Both deep and shallow copies are for reference data types, and there is no difference between deep and shallow copies for the copy of basic data types.
Shallow copy
- Only the outermost layer is copied, it is the same reference for the inner layers.
- For properties of primitive data types, the value is copied. The source object and the copy object open up different memory spaces and do not share them.
- For attributes of a reference data type, the memory address is copied. The source object and the copy object point to the same memory space and share.
Deep copy
- Open up a new space in the heap memory to store data objects. The storage addresses of the source object and the copy object are different and are not shared.
Assignment and shallow copy
Assignment
- Basic data type: Create a piece of data object in the stack memory space, which is referenced by variables, and the memory referenced by different variables is different.
- Reference data type: When assigning a value, the variable refers to an address stored in the stack memory, and this address points to a reference data type object stored in the heap space. That is, the assignment to the variable is assigned to the address, and the data in the heap space is shared.
Difference
Assignment to reference data types differs from shallow copying:
-
When assigning a value, the memory data of this reference data type variable is shared. If its basic data type attribute changes, both the source object and the copy object will change.
-
When shallow copying, for attributes of basic data types, the copied values will not change together if they are not shared. Reference data type attributes, copy addresses, and memory data sharing will change in conjunction.
-
Example:
const source = {<!-- --> A: 'A', referProp: ['A','B','C'] } // assignment const obj1 = source; obj1.A = 'newA'; obj1. referProp[0] = 'newA' console.log(source.A) // newA console.log(source.referProp) // ['newA','B','C'], all changed // shallow copy const obj2 = {<!-- -->...source} obj2.A = 'newA'; obj2.referProp[0] = 'newA' console.log(source.A) // A -- does not change console.log(source.referProp) // ['newA','B','C'] -- change
Shallow copy implementation
In JavaScript, all standard built-in object copy operations (spread syntax, Array.prototype.concat()
, Array.prototype.slice()
, Array. from()
, Object.assign()
and Object.create()
) all create shallow copies.
Expansion operator
const source = {<!-- --> A: 'A', referProp: ['A','B','C'] } // shallow copy const result = {<!-- -->...source}
Object. assign
const source1 = {<!-- --> A: 'A', referProp: ['A','B','C'] } const source2 = {<!-- --> B: 'B' } // shallow copy const result = Object. assign({<!-- -->}, source1, source2) // {A: 'A', referProp: ['A','B','C'], B: 'B'}
Native array methods
Array.prototype.concat()
const arrSource1 = ['A' ,['a','b','c'], 'C'] const arrSource2 = ['a' ,['1','2','3'], 'c'] const result = arrSource1.concat(arrSource2);
Array.prototype.slice()
const arrSource = ['A' ,['a','b','c'], 'C'] const result = arrSource1. slice();
Array. from()
const arrSource = ['A' ,['a','b','c'], 'C'] const result = Array. from(arrSource);
Handwritten
function shallowCopy (target) {<!-- --> // Basic types return directly if (!target || typeof target !== "object") return target; // Judge object or array let result = Array.isArray(params) ? [] : {<!-- -->}; // loop through the copied properties for (let key in target) {<!-- --> if (target.hasOwnProperty(key)) {<!-- --> // only copy target's own properties result[key] = target[key]; } } return result; }
Deep copy implementation
Function library lodash
const _ = require('lodash'); const source = {<!-- --> A: 'A', referProp: ['A','B','C'] }; const result = _.cloneDeep(source);
JSON. stringify
Use JSON.stringify()
to convert a serializable object into a JSON string, then use JSON.parse()
to convert that string back (new) JavaScript object.
const source = {<!-- --> A: 'A', referProp: ['A','B','C'] }; const result = JSON. parse(JSON. stringify(source));
There is a problem
- If there are function, undefined, and symbol in the copied object, they will disappear after being processed by
JSON.stringify()
. - Non-enumerable properties cannot be copied;
- The prototype chain of the object cannot be copied;
- Copying the reference type of
Date
will become a string; - Copying the
RegExp
reference type will become an empty object; - The object contains
NaN, Infinity
and-Infinity
, the result ofJSON
serialization will becomenull
; - A circular reference of an object cannot be copied, i.e. the object is in a loop (
obj[key] = obj
).
Handwritten
Only copy basic data types, array Array, object {}
Reference: Front-end Interview Part 3 JS Road Deep Copy and Shallow Copy – Nuggets (juejin.cn)
// function to detect data type const checkedType = (target) => Object.prototype.toString.call(target).replace(/\[object (\w + )\]/, "$1").toLowerCase(); // implement deep copy (only for Object/Array) const clone = (target, hash = new WeakMap) => {<!-- --> let result; let type = checkedType(target); if (type === 'object') result = {<!-- -->}; else if (type === 'array') result = []; else return target; let copyObj = new target. constructor(); if (hash. has(target)) return hash. get(target); hash.set(target, copyObj) for (let key in target) {<!-- --> if (checkedType(target[key]) === 'object' || checkedType(target[key]) === 'array') {<!-- --> result[key] = clone(target[key], hash); } else {<!-- --> result[key] = target[key]; } } return result; }
Compatible with various types, basically satisfying the use
// to be processed separately // 1. Basic type; // 2. Reference type: array, object, map, set (recyclable) // 3. Non-recyclable types: wrapper types of basic types (Boolean, Number, String, Symbol) and partial reference types (Function, RegExp, Date) // loopable type const traverseTypes = ['array', 'object', 'map', 'set', 'argument']; // function to detect data type const checkedType = (target) => Object.prototype.toString.call(target).replace(/\[object (\w + )\]/, "$1").toLowerCase(); // method to copy RegExp const cloneRegExp = (source) => {<!-- --> const reFlags = /\w*$/; const result = new source.constructor(source.source, reFlags.exec(source)); result.lastIndex = source.lastIndex; return result; } // Copy non-traversable object types const cloneOtherType = (obj, type) => {<!-- --> switch (type) {<!-- --> case 'boolean': case 'number': case 'string': case 'date': return new obj.constructor(obj.valueOf()); case 'symbol': return Object(obj. valueOf()); case 'regexp': return cloneRegExp(obj); case 'function': // function does not handle return obj; } } // implement deep copy const deepClone = (target, map = new WeakMap) => {<!-- --> const type = checkedType(target); // 1. Process basic data types and return directly if (target instanceof Object === false) return target let result; // 2. if (traverseTypes. includes(type)) {<!-- --> // If it is a traversable type, create an empty object directly result = new obj. constructor(); } else {<!-- --> // If not, take special treatment return cloneOtherType(target, type); } // 3. Solve the circular reference problem result = new target. constructor(); if (map.has(target)) return map.get(target); map.set(target, result) /* ----------------Process deep copy of Map and Set---------------- */ // 4. Handle the Map type if (type === 'map') {<!-- --> target.forEach((value, key) => {<!-- --> result.set(key, deepClone(value, map)) }) return result } // 5. Handle the Set type if (type === 'set') {<!-- --> target.forEach(value => {<!-- --> result. add(deepClone(value, map)) }) return result } /* ---------------- Handle array and object {} types ---------------- */ // 6. Handle arrays and objects {} for (let key in target) {<!-- --> if (target.hasOwnProperty(key)) {<!-- --> result[key] = deepClone(target[key], map); } } return result; } const obj = {<!-- --> // basic type str: 'test', num1: 123, boolean: true, sym: Symbol('unique key'), // Reference type (the following 8 data objects need to be deeply copied in the true sense) obj_object: {<!-- --> name: 'squirrel' }, arr: [123, [1, 23, 2], '456'], func: (name, age) => console.log(`name: ${<!-- -->name}, age: ${<!-- -->age}years`), map: new Map([['t', 100], ['s', 200]]), set: new Set([1, 2, 3]), date: new Date(), reg: new RegExp(/test/g), // wrapper class num2: new Number(123), } obj. loop = obj; const objCopy = deepClone(obj); obj. set. add(123) obj.arr[0] = 999 obj.loop.arr[0] = 888 obj.func = [] // Changing the source object will not affect the copy object of the deep copy console.log(obj, 'target') console.log(objCopy, 'result') console.log(obj.loop == obj) // true console.log(objCopy.loop == objCopy) // true
Reference
-
JS handwriting a deep copy from scratch (advanced)
-
https://juejin.cn/post/7134970746580762637
-
https://juejin.cn/post/7188048010511810620