We can compare the loadash library implementation to the js array, number, objects and String operations.

Before we start, let’s clarify the concept of js stack and heap. Both stack and heap are JS data structures. The space of stack is small and used to store basic types of data, such as string and number. Large heap space is used to store large data, such as functions and objects.

When js is executed, a context is created, which is a stack structure

Var a = 1 var b = {} // The context stores the reference address of // key A: value 1 // key B: value objectCopy the code

First, shallow merge

Shallow merge is generally understood as merging only the first layer, instead of recursive merging the same attributes, replacing them directly, as in the following example

let obj1 = {
    a: 'a1'.b: 'b1'.c: {
        d: 'c1'.e: 'c2'}}let obj2 = {
    a: 'a2'.c: {
        d: 'c3'}}console.log(shallowMerge(obj1, obj2))
/ / output
{
    a: 'a2'.b: 'b1'.c: {
        d: 'c3'}}Copy the code

The idea for shallow merging is as follows:

  • If object1 is not an object, replace object1 with object2
  • Return object1 if object1 is an object and object2 is not
  • If object1 is an object and object2 is an object, iterate over object2 to merge and replace the same attribute

Js already implements object. assign for us, but there are some boundary value problems. We can customize the implementation

// Whether typeof is a reference type. In particular, typeof is object and is not null
function isObjectLike(value) {
 return typeof value === 'object'&& value ! = =null
}

/ / use the Object. The prototype. ToString said the Object string, for example [Object Array]
// After ES5, toString can return the correct type. Null corresponds to [object null].
const toString = Object.prototype.toString
function getTag(value) {
 return toString.call(value)
}
// Check if it is a normal object, typeof performance is better
function isPlainObject(value) {
 if(! isObjectLike(value) || getTag(value) ! ='[object Object]') {
   return false
 }
 // For example: object.create (null)
 if (Object.getPrototypeOf(value) === null) {
   return true
 }
 // Loop over the object, returning false if the object is instantiated by a custom constructor
 let proto = value
 while (Object.getPrototypeOf(proto) ! = =null) {
   proto = Object.getPrototypeOf(proto)
 }
 return Object.getPrototypeOf(value) === proto
}
/ / shallow consolidation
 shallowMerge (obj1, obj2) {
     let isPlain1 = isPlainObject(obj1)
     let isPlain2 = isPlainObject(obj2)
    If object1 is not an object, replace object1 with object2
    if(! isPlain1){return obj2
    }
    // 2. If object1 is an object and object2 is not, return object1
    if(! isPlain2){return obj1
    }
    // 3. If object1 is an object and object2 is an object, iterate over object2 to merge
    // object. keys Gets enumerable ordinary keys
    / / Object. GetOwnPropertyNames get all keys in addition to the symbol
    / / Object. GetOwnPropertySymbols obtain symbol key
    / / according to the Object. The assign definition, it will merge Object. The keys and Object. GetOwnPropertySymbols all values
    [...Object.keys(obj2),...Object.getOwnPropertySymbols(obj2)].forEach((key) = >{
        obj1[key] = obj2[key]
    })
    return obj1
}
Copy the code

Second, deep merger

Deep merge is a recursive merge that combines all subattributes of an object, as shown below:

 let obj1 = {
     a: 'a1'.b: 'b1'.c: {
         d: 'c1'.e: 'c2'}}let obj2 = {
     a: 'a2'.c: {
         d: 'c3'}}console.log(deepMerge(obj1, obj2))
 / / output
 {
     a: 'a2'.b: 'b1'.c: {
         d: 'c3'.e: 'c2'}}Copy the code

The deep merge idea is as follows:

  • If object1 is not an object, replace object1 with object2
  • Return object1 if object1 is an object and object2 is not
  • If object1 is an object and object2 is an object, iterate over object2 and merge, recursively merge, and replace the others directly

The only difference with shallow copy is that when assigning a value, it determines the type of value and makes a recursive call

deepMerge (obj1, obj2) {
    let isPlain1 = isPlainObject(obj1)
    let isPlain2 = isPlainObject(obj2)
    if(! isPlain1){return obj2
    }
    if(! isPlain2){return obj1
    }
    [...Object.keys(obj2),...Object.getOwnPropertySymbols(obj2)].forEach((key) = >{
            // The difference from shallow copy
            obj1[key] = deepMerge(obj1[key],obj2[key])
        })
        return obj1
    }
Copy the code

Shallow copy

A shallow copy is a copy of only one layer. A direct reference to a non-basic type in which a change in the properties of any object affects another object, as shown in the following example:

let obj1 = {
    a: 'a1'.c: {
        d: 'c1'}}let obj2 = shallowClone(obj1)
obj1.a = 'a2'
obj1.c.d = 'c2'
console.log(obj2)
/ / output
{
    a: 'a1'.c: {
        d: 'c2'}}Copy the code

Shallow copy implementation ideas

  • Iterate over obj1, and then assign the underlying type value directly to obj2 and the reference address of the reference type to obj2
shallowClone(obj){
    let result = {}
    // for... In iterates through the enumerable properties of an object other than [Symbol], including inherited enumerable properties, in any order
    for (const key in obj){
        // hasOwnProperty returns a Boolean value indicating whether the object has the specified property in its own properties
        if(obj.hasOwnProperty(key)){
           result[key] = obj[key] 
        }
    }
    return result
}
    
Copy the code

Deep copy

Deep copy is a recursive traversal of all the properties of an object, and the values of copied objects do not affect each other. The sample is as follows

let obj1 = {
     a: 'a1'.c: {
         d: 'c1'}}let obj2 = deepClone(obj1)
 obj1.a = 'a2'
 obj1.c.d = 'c2'
 console.log(obj2)
 / / output
 {
     a: 'a1'.c: {
         d: 'c1'}}Copy the code

The simplest deep copy uses Object.parse(Object.stringfy()). The disadvantage is that it cannot handle functions and regex, and the copied value will become null and empty objects.

Deep copy implementation ideas

  • The types of child elements are first classified
  • If it is a base type, it is assigned directly
  • If it is a reference type, it is processed according to the situation
  • Make a recursive copy of a normal object
// List all types
// Base type
const stringTag = '[object String]'
const symbolTag = '[object Symbol]'
const numberTag = '[object Number]'
const boolTag = '[object Boolean]'
const nullTag = '[object Null]'
const undefinedTag = '[object Undefined]'

// Built-in objects
const argsTag = '[object Arguments]'
const arrayTag = '[object Array]'
const dateTag = '[object Date]'
const errorTag = '[object Error]'
const mapTag = '[object Map]'
const objectTag = '[object Object]'
const regexpTag = '[object RegExp]'
const setTag = '[object Set]'
const weakMapTag = '[object WeakMap]'.function deepClone(obj, hash = new WeakMap(a)) { 
    // Prevent circular references, cache reference addresses, and return the result if one occurs
    if (hash.get(obj)) return hash.get(obj);
    
    // The base type and function return the value directly,function copy does not consider this pointing problem
    if(typeofobj ! = ='object' || obj === null) {return obj 
    }
    // Do special processing for dates and regees, and if the data is complex, do response processing for built-in objects that need to be supported
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    
    // Process other reference objects
    let cloneObj = new obj.constructor() 
    hash.set(obj, cloneObj);
    for (let key in obj) { 
        if (obj.hasOwnProperty(key)) { 
            // Implement a recursive copy
            cloneObj[key] = deepClone(obj[key])
        }
    } 
    return cloneObj; 
 }

Copy the code

Deep copy is not perfect, knowing its support level and corresponding advantages and disadvantages to support business functions.