Shallow copy and deep copy

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:

  1. 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.

  2. 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.

  3. 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 of JSON serialization will become null;
  • 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

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture Save it and upload directly (img-6y1YsUIF-1681977942621)(C:\Users\mayonnaise\AppData\Roaming\Typora\typora-user-images\image-20230420153959865.png)]

Reference

  • JS handwriting a deep copy from scratch (advanced)

  • https://juejin.cn/post/7134970746580762637

  • https://juejin.cn/post/7188048010511810620