art-standard-lib
Version:
The Standard Library for JavaScript that aught to be.
201 lines (156 loc) • 7.16 kB
text/coffeescript
"use strict"
2018-07-27 SBD Notes
I want to move towards:
clone: is a combination of stuctural clone and calling .clone, if available. Otherwise, it doesn't do what I'm calling invasive-cloning.
invasiveCloning: false
customCloning: true
recusiveSafe: fully
Note: ArtAtomic objects have .clones that return new objects, however, ArtAtomic objects are designed to be used in a read-only way,
so this is a waste most of the time. UNLESS you intend to modify the cloned object, which sometimes you do need to do.
So, that's why cloneStructure is often the right answer - it won't clone non-plain-objects
cloneStructure: clone only plain-object and arrays. Everything else is just a simple assignment.
invasiveCloning: false
customCloning: false
recusiveSafe: stack - (See below)
invasiveClone:
invasiveCloning: false
customCloning: false
recusiveSafe: full
(cloneByProperties basically did this)
Deeply clone everything, manually cloning objects regardless if they have a .clone method.
---
This current iteration of clone relies on some singleton variables shared across all invocations of clone.
This is fine as long as javascript stays single-threaded.
It also introduces a little bit of uglyness initializing clonedMap necessitating the "top" variable.
FUTURE
A potentially better solution would be to create a new closer each time clone is called at the top-most level,
but when recursing, pass in a new function bound to that closure which is different from the global clone function.
populateClone would need to take an additional argument - the clone function to use for recursive cloning.
Map = require "./Map"
Unique = require './Unique'
{inspect} = require './Inspect'
{isPlainObject, isArray, isFunction} = require './Core/Types'
uniquePropertyName = Unique.PropertyName
clonedMap = null
byStructure = false
byProperties = false
topObject = null
module.exports = class Clone
cloneArray = (array) ->
clonedMap.set array, clonedArray = array.slice()
clonedArray[index] = _clone value for value, index in clonedArray
clonedArray
cloneObject = (obj) ->
clonedMap.set obj, clonedObject = emptyClone obj
if (obj!=topObject || !byProperties) && obj.populateClone
obj.populateClone clonedObject
else
clonedObject[k] = _clone v for k, v of obj
clonedObject
@emptyClone: emptyClone = (obj) ->
if isArray obj then []
else Object.create Object.getPrototypeOf obj
@_clone: _clone = (obj, mode) ->
switch mode
when "byStructure" then byStructure = true
when "byProperties" then byProperties = true
return obj if obj == null || obj == undefined || typeof obj != "object"
return obj if byStructure && !(isArray obj || isPlainObject obj)
if clonedMap
return got if got = clonedMap.get obj
else
topObject = obj
clonedMap = new Map
cloned = switch
when isFunction obj.clone then obj.clone()
when isArray obj then cloneArray obj
when isPlainObject obj then cloneObject obj
if topObject == obj
byStructure = false
byProperties = false
topObject = null
clonedMap = null
cloned
@clone: (obj, mode) ->
if mode?
console.error "2018-07-27: clone mode-param is DEPRICATED. Partial solution, see: cloneStructure"
_clone obj, mode
@cloneByProperties: cloneByProperties = (obj) ->
console.error "2018-07-27: cloneByProperties is DEPRICATED. Partial solution, see: cloneStructure"
_clone obj, "byProperties"
@cloneByStructure: cloneByStructure = (obj) ->
console.error "2018-07-27: cloneByStructure is DEPRICATED. Use: cloneStructure"
_clone obj, "byStructure"
isStructural: isStructural = (obj) -> isPlainObject(obj) || isArray obj
clones plain objects and arrays, but not any other type
FEATURES
- no allocations beyond the newly crearted object and arrays
recursiveSafe: uses a stack
This means:
NO two objects or arrays in the output structure will be "==="
UNLESS they were in a (grand)parent/(grand)child relationship in the source.
This means if you have the same (===) object/array more than once in the structure,
in a non (grand)parent/(grand)child way, each use will get a separate cloned output
in the new structure. In other words
This as a nice advantage: the output is JSON-compatible.
This is mostly a performance optimization. It allows us to avoid any extra object allocations.
Once we can safely use the new ES6 Map everywhere, we might perf-test again to see if full
recursion-safety isn't just as fast.
But then we lose JSON-compatible output guarantees...
###
@cloneStructure: cloneStructure = (inValue) ->
cloningStructurePushed = false
if isPlainObject(inValue) || isA = isArray inValue
_cloneStructure inValue, isA
else inValue
cloneStructureFromStack = []
cloneStructureToStack = []
cloneStructurePush = (inValue, outValue) ->
cloneStructureFromStack.push inValue
cloneStructureToStack.push outValue
true
cloneSturcturePop = ->
cloneStructureFromStack.pop()
cloneStructureToStack.pop()
_cloneStructure = (inObjOrArray, inputIsArray) ->
if 0 <= i = cloneStructureFromStack?.indexOf inObjOrArray
cloneStructureToStack[i]
else
pushed = false
outValue = null
if inputIsArray
outValue = []
for v in inObjOrArray
outValue.push if isPlainObject(v) || vIsArray = isArray v
pushed ||= cloneStructurePush inObjOrArray, outValue
_cloneStructure v, vIsArray
else
v
else
outValue = {}
for k, v of inObjOrArray
outValue[k] = if isPlainObject(v) || vIsArray = isArray v
pushed ||= cloneStructurePush inObjOrArray, outValue
_cloneStructure v, vIsArray
else
v
cloneSturcturePop() if pushed
outValue