Introduction to the

Lodash version 5.0. The main analysis is how to deep clone, the code is simplified after the core code. Basic type, custom clone, error handling is not considered.

Distinguish object types

Through the Object. The prototype. ToString. Call to get the detailed type of the tag. Arrays go through array.isarray.

const toString = Object.prototype.toString
function getTag(value) {
  if (value == null) {
    return value === undefined ? '[object Undefined]' : '[object Null]'
  }
  return toString.call(value)
}
Copy the code

Clone reference type

There are two methods for copying objects:

  • The constructor
    • new targetObject.constructor()
      • Advantages:
        • The constructor generates the object, guaranteeing property integrity.
          • In copying built-in objects, for exampleArray.MapEtc. There are internal JS attributes that cannot be created. Can only be generated by constructors.
        • The prototype chain is correct.
      • Disadvantages:
        • The performance overhead of executing a function once.
  • The prototype
    • Object.create(Object.getPrototypeOf(targetObject))
      • Advantages:
        • Low performance overhead.
        • The prototype chain is correct.
      • Disadvantages:
        • Unable to copy internal JS properties.

Cloned array

The length of the array is a special internal JS property that must be created using the constructor. Special case: array of return values of regex.exec(). Note: Why not just use new Array(length)?

  • Because it could be an inheritanceArrayclassCreate an array.
    • Such as:Vue2In the responsive principle, created by the array.
      • class MyArray extends Array {}, proxies some array methods.
function initCloneArray(array) {
  const { length } = array
  const result = new array.constructor(length)
  // regex.exec() returns an array of values, requiring special handling of index input
  if (length && typeof array[0= = ='string' && hasOwnProperty.call(array, 'index')) {
    result.index = array.index
    result.input = array.input
  }
  return result
}
Copy the code

Clone normal objects and Arguments

Note: Both constructor and object prototype can be modified, so use judgment.

function initCloneObject(object) {
  //constructor is a function
  return (typeof object.constructor === 'function' && !isPrototype(object))
    ? Object.create(Object.getPrototypeOf(object))
    : {} // Special case, direct creation of object prototype chain missing
}
Copy the code

One more special case: Chrome already supports class private properties, methods. With prototype cloning, private properties cannot be created and an error is reported when called.

// called from a browser that supports native private properties. Babel conversion does not have this problem. But that's not a native private property.
class Test {
  #a=1
  print(){
    console.log(this.#a)
  }
}
let o1 = new Test()
o1.print()/ / 1

let o2 = initCloneObject(o1)
o2.print()// The error has no private attribute. Uncaught TypeError: Cannot read private member #a from an object whose class did not declare it
Copy the code

Created by the constructor, this problem does not exist.

let o3 = new o1.constructor()
o3.print() / / 1
Copy the code

Lodash was prototyped with performance concerns (reduced constructor execution) in mind.

This particular case has not been handled by LoDash and is currently buggy.

Clone the RegExp object

Retrieves the re and the regular expression flag.

function cloneRegExp(regexp) {
  const result = new regexp.constructor(regexp.source, reFlags.exec(regexp))
  // the g flag is used to start the next matching index value
  result.lastIndex = regexp.lastIndex
  return result
}
Copy the code

Clone Symbol object

Symbol.prototype.valueOf returns the original value. When calling Object, an Object of guaranteed type is constructed for a value of primitive type.

const symbolValueOf = Symbol.prototype.valueOf
function cloneSymbol(symbol) {
  return Object(symbolValueOf.call(symbol))
}
Copy the code

Remaining reference types

Not one analysis, look at the code, the principle is the constructor.

function initCloneByTag(object, tag, isDeep) {
  const Ctor = object.constructor
  switch (tag) {
    case arrayBufferTag:
      return cloneArrayBuffer(object)

    case boolTag:
    case dateTag:
      return new Ctor(+object)

    case dataViewTag:
      return cloneDataView(object, isDeep)

    case float32Tag: case float64Tag:
    case int8Tag: case int16Tag: case int32Tag:
    case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
      return cloneTypedArray(object, isDeep)

    case mapTag:
      return new Ctor

    case numberTag:
    case stringTag:
      return new Ctor(object)

    case setTag:
      return new Ctor
  }
}
function cloneArrayBuffer(arrayBuffer) {
  const result = new arrayBuffer.constructor(arrayBuffer.byteLength)
  new Uint8Array(result).set(new Uint8Array(arrayBuffer))
  return result
}
function cloneDataView(dataView, isDeep) {
  const buffer = cloneArrayBuffer(dataView.buffer)
  return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength)
}
function cloneTypedArray(typedArray, isDeep) {
  const buffer = cloneArrayBuffer(typedArray.buffer) 
  return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length)
}


Copy the code

Recursive cloning

Recursive clones, arrays and objects

Arrays are traversal items. Objects are traversal properties.

Retrieve attributes

Gets object properties. ** Note: the ** class array object, symbol as the property key

function getAllKeys(object) {
  / / get the keys
  const result = keys(object)
  if (!Array.isArray(object)) {
    // Handle symbol attributes for particularitiesresult.push(... getSymbols(object)) }return result
}
// Use object.keys for objects
// Class array object, eg: arguments for in take key
// all return keys
function keys(object) {
  return isArrayLike(object)
    ? arrayLikeKeys(object)
    : Object.keys(Object(object))
}
// Whether the attribute is enumerable
const propertyIsEnumerable = Object.prototype.propertyIsEnumerable
// An array of all Symbol attributes for the object itself.
const nativeGetSymbols = Object.getOwnPropertySymbols
// Get symbol as key property
function getSymbols(object) {
  if (object == null) {
    return[]}// Basic type of packaging,
  object = Object(object)
  return nativeGetSymbols(object).filter((symbol) = > propertyIsEnumerable.call(object, symbol))
}

Copy the code

traverse

// Value is the cloned object
// Array iterates over itself. Object traverses an array of properties.
const props = isArr ? undefined : getAllKeys(value)
// 
arrayEach(props || value, (subValue, key) = > {
    // subValue is array[index], key is index
    if (props) {
      // For the object key is array[index]
      key = subValue
      / / value is value [lkey]
      subValue = value[key]
    }
    // Assign the recursively traversed value to key. For special cases, eg: NAN is also assigned to NAN
    Object [key] = baseClone(subValue, bitmask, customizer, key, value, stack)
    assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack))
})
Copy the code

Recursive clones, maps and sets

Let’s do the same thing. But use set/add to assign values.

if (tag == mapTag) {
  value.forEach((subValue, key) = > {
    result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack))
  })
}
if (tag == setTag) {
  value.forEach((subValue) = > {
    result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack))
  })
}
Copy the code

Handling circular references

How it works: Caches each reference and then determines if it has been cloned when the clone function is called. If yes, the clone reference is returned. If no, continue cloning. Lodash has two modes for caching references.

Cache references

How do I store references? Array or Map.

The array cache references ListCache

Each item is an array (length 2) [old object reference, clone object reference]. [[old object reference, clone object reference], [old object reference, clone object reference], [old object reference, clone object reference],…] Then to determine the cache, just iterate over the number group.

So here’s the simplest demo we can do

class ListCache {
  __data__ = []
  size = 0

  get(key) {
    const data = this.__data__
    const index = data.findIndex(([cacheKey]) = >cacheKey===key)
    return index < 0 ? undefined : data[index][1]}has(key) {
    const data = this.__data__
    return data.findIndex(([cacheKey]) = >cacheKey===key) > -1
  }

  set(key, value) {
    const data = this.__data__
    const index = data.findIndex(([cacheKey]) = >cacheKey===key)
    if (index < 0) {
      ++this.size
      data.push([key, value])
    } else {
      data[index][1] = value
    }
    return this}}Copy the code

For caches less than 200 in loadsh, use ListCatch and MapCatch

MapCatch

Use native Map objects.

Cloning function

Loadsh does not clone functions and will return {}. Since cloning functions makes no practical sense, it’s ok to share the same function. And there are problems with corrification, this, closures, etc., and you can’t clone functions of equal value.

But the interview will ask, clone one.

Cloning function

new Function('return ' + fn.toString())() 
/ / or
eval(` (${fn.toString()}) `)
Copy the code

conclusion

  • Distinguish object tag
  • Clone objects through a constructor or prototype chain
  • ArrayMapResolving circular references
  • recursive
  • Get object properties
  • Cloning function