4 ms·
Let me attempt to improve upon Fowler's ValueObject pattern in JS, and tell you the upsides: function Point(x, y) { const hash = Point.__hash(x, y)
by wwwigham 10y ago
Let me attempt to improve upon Fowler's ValueObject pattern in JS, and tell you the upsides:
function Point(x, y) {
const hash = Point.__hash(x, y);
const cached = Point.__cache.get(hash);
if (cached) return cached;
const newPoint = {get x() { return x; }, get y() { return y; } };
Point.__cache.set(hash, newPoint);
return newPoint;
}
Point.__hash = function(ptx, pty) {
return `${ptx}|${pty}`;
}
Point.__cache = new WeakMap();
This does what you would expect a ValueObject (or an algebraic type or struct depending on the languages you normally use) to do in another non-JS language - it interns all equivalent objects so that simple reference equality is sufficient to determine equality - rendering the nonstandard "equals" method unneeded. This also solves all the issues with ".includes" and so forth, again, because the built in reference equality is sufficient to determine equality. This also has much better memory characteristics in a system where many equivalent objects are created, as only one copy is ever stored in memory.
The only drawback is the small overhead of the WeakMap used to cache all the references and the overhead of "hashing" at object creation time - neither of which should be noticeable in most applications, and the memory benefits should outweigh these concerns in most performance applications regardless.
- phpnode 10y agoUnfortunately your example won't work. You can't use a primitive value as a key in a weak map, which negates many of their possible use cases.
- wwwigham 10y agoYou're right, I hadn't realized that the spec forbade primitives as WeakMap keys - so while to approach should actually work in other languages, in JS it would leak memory as you'd be forced to use a normal map (or clean up after yourself - ich).
- tylorr 10y agoYeah, weak map have weak references to the keys, not the values.
- jstimpfle 10y agoNo. Non-reference plain old structs are typically more efficient for most operations, up to a certain size (how large, 16, 32, 64 bytes?). Objects have a certain overhead, for example each reference uses 8 bytes, and they typically come with a vtable. Also cache efficiency is very important these days. Sharing is detrimental unless memory consumption is an issue. And let's not think about GC overhead. A point class holding two integers is a really bad idea when you have millions of points. Conversely, as objects get larger, it's less likely for two objects to be equal. Interning is just a bad idea for most cases.