Retrieved typed objects from IndexedDB - javascript

What is the most efficient way to store and retrieve typed Javascript objects in IndexedDB?
The problem is that IndexedDB doesn't store prototype information, so you can only store and retrieve plain objects (or arrays or primitives or a few other types). A workaround I came up with is to explicitly assign __proto__ on objects retrieved from the database. For example, to get a Game object I do
game.__proto__ = Game.prototype;
However, __proto__ assignment has the problem that it is A) technically nonstandard, though supported in practice, and B) deoptimizes the code. In fact, Firefox gives an explicit warning
mutating the [[Prototype]] of an object will cause your code to run very slowly; instead create the object with the correct initial [[Prototype]] value using Object.create
Obviously, Object.create isn't a possibility here. Are there any better alternatives to __proto__ assignment?

You could consider storing just the backing-data object itself. Game would become just a proxy for a storable object.
function Game(props) {
this.props = props || {};
}
// An example of property decoration
Game.prototype.set x(value) {
this.props.x = value;
};
Game.prototype.get x() {
return this.props.x;
};
// Use this when initializing a game after retrieving game data from indexedDB store.
// e.g. when creating a new game, use var newGame = Game.fromSerializable(props);
Game.fromSerializable = function(props) {
return new Game(props);
};
// When it comes time to persist the game object, expose the serializable props object
// so that the caller can pass it to store.put/store.add
Game.prototype.toSerializable = function() {
return this.props;
};
This might be simpler than bothering to deal with what can pass through the structured clone algorithm used by indexedDB for read/write, or using some strange one-off hack that other people might struggle to understand.

Related

Is WeakMap performance influenced by the Key Object?

When using a WeakMap Object in JavaScript, the get and set methods require a Key to be passed in as argument.
FYI, I'm using WeakMap to simulate private properties for a class, something like this:
window.MyObject = (function(){
let _privateProperty = new WeakMap();
class MyObject {
constructor(value) {
_privateVariable.set(this, value);
}
SamplePrivateGet() {
return _privateVariable.get(this);
}
}
return MyObject;
})();
My question is: is get and set performance influenced by the object used as key?
I know primitive types can't be used as key, but maybe using an object with only one property as key could be faster than using an object that has, say, one thousand properties.
I tried looking at ECMA specifications but it's not specified, my guess is it's because it would depends on the browser's implementation.
EDIT: There are two ways to approach this with WeakMaps, one is declaring a single _privateProperties WeakMap to which I will add my private properties, the other declare one WeakMap for each of the private properties. I'm currently using the second approach so that every WeakMap can be Garbage Collected separately, but maybe going with the first would allow me to make much less .get calls. But I guess that's out of the scope of the question :)
Not sure if this is good idea, but you could make kind of private instance properties by using Symbols.
const PRIVATE_VAR = Symbol('MY_CLASS::PRIVATE_VAR');
const PRIVATE_FUNC = Symbol('MY_CLASS::PRIVATE_FUNC');
export default class MyClass{
constructor(value){
this[PRIVATE_VAR] = value;
}
[PRIVATE_FUNC](){
/* ... */
}
}
Without access to those Symbols values, it is quite hard to access those specific instance properties (plus instance properties defined with Symbols are non-enumerable in classes and objects).
btw 'Private instance methods and accessors' are at stage 3 so such solutions might not be relevant soon
https://github.com/tc39/proposals

Why inheriting from Array is difficult to implement in ES5?

With prototype inheritance in ES5, it looks not trivial to inherit from Array and get the expected behavior, like updating .length automatically when adding item to Array (see below code). ES5 creates object of the derived function (MyArray) then pass it base type to do initialization, why is this model hard to get the expected behavior in this model?
ES6 changed the behavior and creating object in base constructor, and constructor of derived class initialize it after that (after call to super()), wondering why this solved the problem.
function MyArray(){}
MyArray.prototype = Object.create(Array.prototype);
var myArr = new MyArray();
myArr[0] = 'first';
console.log(myArr.length); // expect '1', but got '0' in output
The key thing about Array is that a real array object is an Array Exotic Object. An exotic object is an object that has behavior that could not be achived using standard JS language features, though in ES6 Proxy allows much more ability for user code to create exotic-like objects.
When subclassing a constructor that returns an exotic object like Array, the subclassing method needs to be done in such a way that the object created is actually an exotic object. When you do something like
function ArraySubclass(){}
ArraySubclass.prototype = Object.create(Array.prototype);
then
(new ArraySubclass()) instanceof Array
because the prototype matches up, but the object returned by new ArraySubclass is just a normal object that happens to have Array.prototype in its prototype chain. But you'll notice that
Array.isArray(new ArraySubclass()); // false
because the object isn't a real exotic. In this case
new ArraySubclass()
is identical to doing
var obj = Object.create(ArraySubclass.prototype);
ArraySubclass.call(obj);
So in ES5 how do you extend Array? You need to create an exotic object, but you also need to ensure that the exotic object has your ArraySubclass.prototype object in its prototype chain. That is where ES5 hit it issues, because in vanilla ES5, there is no way to change an existing object's prototype. With the __proto__ extension that many engines added you could get the correct Array subclassing behavior with code like
var obj = new Array();
obj.__proto__ = ArraySubclass.prototype;
ArraySubclass.call(obj);
Say you wanted to generalize the pattern above, how would you do it?
function makeSubclass(baseConstructor, childConstructor){
var obj = new baseConstructor();
obj.__proto__ = childConstructor.prototype;
return obj;
}
function ArraySubclass(){
var arr = makeSubclass(Array, ArraySubclass);
// do initialization stuff and use 'arr' like 'this'
return arr;
}
ArraySubclass.prototype = Object.create(Array.prototype);
so that works in ES5 + __proto__, but what about as things get more complicated? What if you want to subclass ArraySubclass? You'd have to be able to change the second the second parameter of makeSubclass. But how do we do that? What is the actual goal here? When you do something like
new ArraySubclass()
it is the value passed to new that we care about as that second parameter, and it is that constructor's prototype that should be getting passed along. There is no nice avenue in ES5 to accomplish this.
This is where ES6 classes have a benefit.
class ArraySubclass extends Array {
constructor(){
super();
}
}
The key thing is that when super() runs, it knows that ArraySubclass is the child class. When super() calls new Array, it also passes along an extra hidden parameter that says "hey, when you create this array, set its prototype to ArraySubclass.prototype. If there are many levels of inheritance, it will pass along the child-most prototype so that the returned exotic object is a real exotic while also making sure it has the correct prototype.
Not only does this mean that things are constructed properly, but it means that engines can create the object with the correct prototype value up front. Mutating an object's __proto__ value after creating is a well-known deoptimization point because of the ways engines process and track objects.

Is it ok to create a prototype just to save internal object properties?

When I instantiate an object in my javascript framework, I add a __listeners__ property to it. But then the console.log output shows the__listeners__ property first.
I don't want the __listeners__ property showing up in the beginning of the console.log output, since its an internal property in my framework. I'm looking for a way to somehow push the __listeners__ property to the end of the object.
The only solution I have so far is by adding an extra object in the prototype chain.
create: function() {
var objTemplate=Object.create(this);
var newObj=Object.create(objTemplate);
objTemplate.__listeners__=[];
return newObj;
}
But, the above solution, will cause slower lookups through the prototype chain and creates a slightly unintuitive object hierarchy.
Is this ok or is there a better solution to this problem?
If you can use ES2015's WeakMap, which cannot be shimmed on older environments (but see ¹), creating information related to an object that isn't stored on the object is one of their use cases:
const privateDataCache = new WeakMap();
class MyThingy {
constructor() {
privateDataCache.set(this, {listeners: []});
// ...
}
addListener(listener) {
privateDataCache.get(this).listeners.push(listener);
}
removeListener(listener) {
const data = privateDataCache.get(this);
data.listeners = data.listeners.filter(l => l !== listener);
}
}
Because the keys in a WeakMap are weakly-held, they don't prevent your objects from being garbage collected, and the private data stored in the WeakMap is removed when the key becomes invalid.
That won't work in IE prior to IE11, though. (And IE11's WeakMap is missing some things, but nothing the above relies on.)
¹ Although WeakMap cannot be correctly shimmed/polyfilled, the syntax can be shimmed/polyfilled. core.js does that, implementing the WeakMap by storing data on the key itself. So while not correct, it may well be what you want for your use case.

ES6 Set, WeakSet, Map and WeakMap

There is already some questions about map and weak maps, like this: What's the difference between ES6 Map and WeakMap? but I would like to ask in which situation should I favor the use of these data structures? Or what should I take in consideration when I favor one over the others?
Examples of the data structures from:https://github.com/lukehoban/es6features
// Sets
var s = new Set();
s.add("hello").add("goodbye").add("hello");
s.size === 2;
s.has("hello") === true;
// Maps
var m = new Map();
m.set("hello", 42);
m.set(s, 34);
m.get(s) == 34;
// Weak Maps
var wm = new WeakMap();
wm.set(s, { extra: 42 });
wm.size === undefined
// Weak Sets
var ws = new WeakSet();
ws.add({ data: 42 });
// Because the added object has no other references, it will not be held in the set
Bonus. Which of the above data structures will produce the same/similar result of doing: let hash = object.create(null); hash[index] = something;
This is covered in §23.3 of the specification:
If an object that is being used as the key of a WeakMap key/value pair is only reachable by following a chain of references that start within that WeakMap, then that key/value pair is inaccessible and is automatically removed from the WeakMap.
So the entries in a weak map, if their keys aren't referenced by anything else, will be reclaimed by garbage collection at some point.
In contrast, a Map holds a strong reference to its keys, preventing them from being garbage-collected if the map is the only thing referencing them.
MDN puts it like this:
The key in a WeakMap is held weakly. What this means is that, if there are no other strong references to the key, then the entire entry will be removed from the WeakMap by the garbage collector.
And WeakSet does the same.
...in which situation should I favor the use of this data structures?
Any situation where you don't want the fact you have a map/set using a key to prevent that key from being garbage-collected. Here are some examples:
Having instance-specific information which is truly private to the instance, which looks like this: (Note: This example is from 2015, well before private fields were an option. Here in 2021, I'd use private fields for this.)
let Thing = (() => {
var privateData = new WeakMap();
class Thing {
constructor() {
privateData[this] = {
foo: "some value"
};
}
doSomething() {
console.log(privateData[this].foo);
}
}
return Thing;
})();
There's no way for code outside that scoping function to access the data in privateData. That data is keyed by the instance itself. You wouldn't do that without a WeakMap because it would be a memory leak, your Thing instances would never be cleaned up. But WeakMap only holds weak references, and so if your code using a Thing instance is done with it and releases its reference to the instance, the WeakMap doesn't prevent the instance from being garbage-collected; instead, the entry keyed by the instance is removed from the map.
Holding information for objects you don't control. Suppose you get an object from some API and you need to remember some additional information about that object. You could add properties to the object itself (if it's not sealed), but adding properties to objets outside of your control is just asking for trouble. Instead, you can use a WeakMap keyed by the object to store your extra information.
One use case for WeakSet is tracking or branding: Suppose that before "using" an object, you need to know whether that object has ever been "used" in the past, but without storing that as a flag on the object (perhaps because if it's a flag on the object, other code can see it [though you could use a private field to prevent that]; or because it's not your object [so private fields wouldn't help]). For instance, this might be some kind of single-use access token. A WeakSet is a simple way to do that without forcing the object to stay in memory.
Which of the above data structures will produce the same/similar result of doing: let hash = Object.create(null); hash[index] = something;
That would be nearest to Map, because the string index (the property name) will be held by a strong reference in the object (it and its associated property will not be reclaimed if nothing else references it).

What is the use of creating a class in javascript, if I can insert any attritube to an object any time

Consider the below code.
I created an empty object. And am adding var1,var2,var3 without declaring them.
var har = new Object();
har.var1 = "Var1";
har.var2 = "Var1";
har.var3 = "Var1";
alert( har.var1 );
If I can do this, then why do I need to create a Class and fix the attributes when I can introduce new attributes anytime?
Why would you even need to use objects in the first place? Non-object-oriented languages are already Turing-complete, so everything you can accomplish with objects you can also do without them.
What you're showing in your example is not really an object, but just a dictionary. In cases like this, where you only need to keep several related properties together, anonymous unprototyped objects like the one you're using are the de-facto standard approach (though it is customary to initialize them with the shorthand syntax, e.g. var har = {}). It is an object, since it uses the object data structure, but it is not object-oriented.
Actual objects, in contrast, not only define data, but also the operations that you can perform on that data. Objects have not only properties, but also methods which work on these properties. These properties are usually defined in the object prototype (which you're calling "class", but Javascript is not a class-based language, but a prototype-based one). All methods defined in the prototype are shared between all instances of that prototype.
function Counter() {
this.counter = 0;
}
Counter.prototype.increment = function() {
this.counter++;
alert(this.counter);
}
var c1 = new Counter();
var c2 = new Counter();
c1.increment(); // alerts 1
c1.increment(); // alerts 2
c2.increment(); // independent from c1: alerts 1 again
Each instance is still a dictionary, as in your example (and you can even still add more properties to them, they are not "fixed" by having a constructor and prototype), but now it can also perform tasks on its properties. This can be done your way as well:
c1 = {
counter: 0,
increment: function() {
this.counter++;
alert(this.counter);
}
}
c2 = {
counter: 0,
increment: function() {
this.counter++;
alert(this.counter);
}
}
You can see, however, that if you need two counters, without using prototypes you will need to duplicate your entire object definition. This will be cumbersome to write and maintain, and each increment function will be defined separately, thus it will also waste memory.
That said, in cases where you need an object that you know you'll only ever need one instance of, it makes no sense to define a constructor and a prototype for it. Such objects are usually regarded as namespaces instead of actual objects.
Appendix: Dictionaries vs Objects
A dictionary is a data structure which holds named elements. Besides "dictionary", they are also called associative arrays or hashmaps. Objects in Javascript are implemented as dictionaries — each property is a named element in the dictionary. In addition to a plain dictionary, objects also have a prototype, which is kind-of like a parent object — when you look up a named element in the dictionary and it is not there, it is automatically searched for in the prototype as well. This way, default properties and methods are defined only once in the prototype, and do not need to be copied into each instance. (The prototype of an object is often visible as the instance.__proto__ property, though this is non-standard and deprecated even in browsers that support it; the actual prototype is defined as an internal, non-accessible property by the standard)
So, Javascript objects are actually dictionaries. When you want to use a plain dictionary to store some related properties together, there is no separate syntax in Javascript to create a dictionary that is not an object, so you create an instance of the base Object type to hold your dictionary (it does not matter if you do var dict = new Object or var dict = {}, the result is the same); thus, dictionaries that you use in your code are also objects.

Categories

Resources