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 example
Array
.Map
Etc. There are internal JS attributes that cannot be created. Can only be generated by constructors.
- In copying built-in objects, for example
- The prototype chain is correct.
- The constructor generates the object, guaranteeing property integrity.
- Disadvantages:
- The performance overhead of executing a function once.
- Advantages:
- The prototype
Object.create(Object.getPrototypeOf(targetObject))
- Advantages:
- Low performance overhead.
- The prototype chain is correct.
- Disadvantages:
- Unable to copy internal JS properties.
- Advantages:
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 inheritance
Array
的class
Create an array.- Such as:
Vue2
In the responsive principle, created by the array.class MyArray extends Array {}
, proxies some array methods.
- Such as:
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
Array
或Map
Resolving circular references- recursive
- Get object properties
- Cloning function