In JavaScript, value copying is the fundamental basis of all copying. Both shallow and deep copying rely heavily on value copying. So if you want to copy, it's imperative that you understand value copying. Meouzer
Full code is at ValueCopy.js, which depends on typing.js. We will not cover everything in valueCopy.js.
The goal is to produce a ValueCopy() function that will serve as the basis for shallow and deep copying. The value copy of a source object is a target object whose internal state is the same but without any properties of its own: That's the way the concept was originally conceived, however JavaScript may force the value copy to have properties, but that's not a problem since after a copying source to target the properties are what they should be.
A value copy primitive is an element x for which ValueCopy(x) is supposed to be x. The value-copy-primitives are explicitly listed below.
It's also impossible to read the internal states of programmer defined class instances, but instead of being a value-copy-primitives, we will throw exceptions on encountering such because they are just too problematic.
The most arcane JavaScript elements treated in this article are null objects, which you can read about in Object Inheritance. The most arcane class is the fake Arguments class, which you can read about in JavaScript Data Typing isn't as Simple as you Think.
Of particular note, if x is an ArrayBuffer, a DataView, a Set or a Map, then a copy of x is simply x.valueCopy(). The same holds for all other built in classes except for Array and all the typed arrays. (Implicitly understood WeakMap and WeakSet excluded also).
If x is an array x.valueCopy(x) is the empty Array []. That's because indices are public properties of x and so the empty array has the same internal state as x but without any properties of its own. The next step in the copying process after valueCopy() will be to fill in the entries of the empty Array.
To show how ValueCopy can be used to shallow copy every type of built in class instance, and pretty much every object except for programmer defined class instances and value-copy-primitives, look at the following code.
Of particular note, if x is an ArrayBuffer, a DataView, a Set a Map, an Array, a typed array or any other built in class then a copy of x is simply shallowCopy(x).
To eliminate the need for a huge switch statement in ValueCopy() to handle all the numerous built in classes, it's necessary to prototype ValueCopy(), in uncapitalized form, for the same numerous built in classes. Then ValueCopy() will employ those prototyped versions of valueCopy() and tie up some loose ends. To avoid naming conflict bugs in both node.js and the browsers, the name of the prototyped versions must be uncapitalized.
For example, to define valueCopy for Booleans we need to define Boolean.prototype.valueCopy. This obviously defines the valueCopy property of Boolean.Prototype itself. What should the value be? Well in a copy stream it doesn't hurt to copy built in class prototypes as is. So Boolean.prototype.valueCopy() is Boolean.prototype. Besides why would you want to valueCopy/shallow-copy/deep-copy Boolean.prototype even if you could?
If x is not a class instance of some class, and not a value-copy-primitive, then the value copy of x is y = Object.create(Object.getPrototypeOf(x)). Basically the internal state of x and y consist solely of their internal prototypes and those internal prototypes are equal.
Basic Class | Class Instance x | Internal State | x.valueCopy() | Number Properties |
Boolean | x = new Boolean(true) | true | new Boolean(x.valueOf()) | 0 |
---|---|---|---|---|
Number | x = new Number(7) | 7 | new Number(x.valueOf()) | 0 |
String | x = new String("cat") | "cat" | new String(x.valueOf) | 0 |
Date | x = new Date() | 1617286697028 | new Date(x.valueOf()) | 0 |
RegExp | x = new RegExp(/abc/) | /abc/ | new RegExp(x.valueOf()) | 1 |
Set | x = new Set(["cat", "dog"]) | elements "cat", "dog" | new Set(x.valueOf()) | 0 |
Map | x = new Map([["a","b"],["c","d"]]) | "a" mapped to "b" and "c" mapped to "d" | new Map(x.valueOf()) | 0 |
If x = new RegExp(/abc/) then y = new RegExp(x.valueOf()) has a single property lastIndex with a value possibly different than that for x. However, once the properties of x are transferred to y, the lastIndex properties of the two will be the same.
If source = new Boolean(true) then target = new Boolean(source.valueOf()) is a value copy of source. It has the same internal state true as the source and has no properties of its own. A similar discussion holds for all remaining classes of the table.
If x is an instance of one of the error classes then x.valueCopy() will be an actual copy of x. This breaks the theme that the value copy should be empty, but makes the value copy more useful.
All typed array class instances are fixed length arrays whose entries are accessible by index notation. If x is an instance of a typed array class then x.valueCopy() is an instance of the same class, and with the same fixed length as x. However, the entries of the value copy are all initialized to zero.
The two indeterminate classes are WeakSet and WeakMap. There is no way to determine what the members of a WeakSet instance are. Thus it is impossible to duplicate a WeakSet instance. Since it's impossible to copy a WeakSet instance, a WeakSet instance is taken as a value-copy-primitive. A similar discussion holds for WeakMap.
If x is an array then y = x.valueCopy() is an empty array [ ]. We don't want entries for the value copy because the next step in the copying process is to transfer the properties of x to y, which then fills in the empty array.
Let dv1 be a DataView. dv1.buffer and dv1.buffer.valueCopy() are equivalent ArrayBuffers. Let dv2 = DataView(dv1.buffer.valueCopy()). Then dv2.buffer = dv1.buffer.valueCopy() is equivalent to dv1.buffer. Thus dv2 is a value copy of dv1. They both have the same internal state, and dv2 doesn't introduce any new properties.
In general, value-copying/copying/deep-copying with functions involved is hopeless. That's because in general you don't know in advance details about a function's outer context. It is therefore imposssible to write an evaluator to copy the function into an appropriate context. Also multiple functions may enter the copy stream and it is highly unlikely a single evaluator will work for them all.
The prototyed version of valueCopy is Function.prototype.valueCopy(evaluator). When you write func.valueCopy(evaluator) just any evaluator won't do. You have to examine the details of the outer context of func and actually write a custom evaluator before you plug it into func.valueCopy().
However, it is actually possible that for a particluar programmer defined class, the class constructor can provide a single custom evaluator that works for all the instance methods and private functions and so deep copying a class instance will be possible if the class doesn't get too wild.
The valueCopy() function will copy class instances of the built in classes, no problem. That is except for the Array and typed array classes. The shallowCopy() function will copy class instances of all built in classes. shallowCopy() will also copy the property descriptors.
Of lesser interest, valueCopy() will copy all objects whose internal state consists solely of its __proto__ attribute. If x is such an object then Object.create(Object.getPrototypeOf(x)) is a value copy of x. Examples that cover the full gambit of such objects follow. Klass is either a built in class or a programmer defined class.