Saving and restoring objects in Javascript is hard, here I propose a solution, a practice to create savable Javascript object.
Let says you have an object like this:
var =
If you pass it to JSON.stringify
, it gives you something like this
Clearly something is missing.
The problem is, the default behavior of stringify ignores functions, even though (function(){}) instanceof Object == true
. But it makes sense because functions are difficult to save. For example native function, Math.min
itself is a anonymous native function, calling toString on it will result in function min() { [native code] }
which doesn’t provide any information. Functions can also access outer scope, it is really hard to save everything the function will potentially access.
To make sure your objects are savable, data and functions should be separated. One good way is to store functions in the prototype object. This also reduce memory usage because every instance of the same class shares the same function.
var ;
Human.prototype = ;
var = new; // Human created: Alice
; // Hello my name is Alice.
; // {"name":"Alice", "age":10}
Now the generated JSON still doesn’t include the greet function, but it makes sense. The greet function doesn’t belongs to alice, it belongs to Human.prototype. So the problem now becomes How to save the prototype information. Well, we can create a special initializer key to store the prototype information.
Human.prototype. = ; // now every human instance knows they are human
alice. // Human
The initializer is still in prototype so it won’t be added to JSON.
Luckily, we have a toJSON method that can be used to customize the JSON representation of an object. So we can add the initializer ourself.
// So starting from now:
; // {"initializer":"Human","name":"Alice","age":10}
// it stores the initializer!
We can definitely do something like this:
var = ;
;
While it works most of the time, you may notice that it prints “Human created: undefined” duration the restore process. It is because the restore function knows nothing about the initializer, so it could not restore correctly. In order to make the restore process more customizable, I think it would be better to create an optional restore
function in the initializer.
;
Now it works as expected.
Subclassing can be done like this:
Student.prototype = ;
Student.prototype. = ;
var = new;
var = ;
restored instanceof Student // true
// Hello my name is Alice. I study in HK School
By abusing the fact that Object.prototype
can be modified, we can add the above functions directly to the root of prototype chain, like this:
;
Here is an enhanced version of restore, adding support for namespaced initializer like new Some.thing.here()
:
;
Then we can restore objects like this:
var = .;
Let says you have a custom object such as an Image.
var ;
var = newhttp://example.com/image.jpg");
The image object cannot be saved easily, because it is a circular referenced object (All DOM are circular referenced), and you cannot modify its prototype chain. However these problems can be solved by overriding the toJSON method.
This is a brief introduction of my solution on making savable Javascript objects. Here at Novelsphere, we are using a promise-compatible version (So the restore process can be async) of the above structure to make our game engine, to make sure games save and load properly.