Whenever we send objects to the server, we need to serialize them, for example using JSON encoding. Any safe object encoding only serializes the data, not the code.
1 | var foo = { |
So the objects we receive are just data without any methods. It would be very nice to attach methods to the objects. Otherwise all functional logic has to be external, passed around anywhere the objects go:
1 | function printName(person) { console.log(person.first, person.last); } |
We can add methods directly to each new object by copying them another object, using _.assign for example
1 | var person = JSON.parse('{"first":"Joe","last":"Smith"}'); |
This approach takes too much memory: each method reference takes up space.
JavaScript has an interesting alternative: it is prototypical language, meaning
an object has a prototype property pointing at another object.
If we access a property or a method and it cannot be found on
the object itself, the environment tries to find on its prototype, then prototype's prototype, etc.
This is why every object (with the exception of
bare objects)
has method .toString
1 | var person = JSON.parse('{"first":"Joe","last":"Smith"}'); |
This points a way to easily add without copying methods / properties to any object
parsed from JSON string. In environments where an object's prototype can be changed via
__proto__ or ES6 setPrototypeOf we can directly set:
1 | var person = JSON.parse('{"first":"Joe","last":"Smith"}'); |
We are not copying toString reference, instead we only repointed prototype
from Object.prototype to printer. At any point we can change the single
method in printer and every object changes its behavior.
1 | person.__proto__ = printer; |
Changing __proto__ is considered bad for performance (I have not checked myself),
and seems to be unavailable in some browsers (IE < 11). In these cases, either assign the
object or construct a copy with new prototype using Object.create
1 | person = _.assign(Object.create(printer), person); |
Finally, let us look at JSON.parse function itself. It accepts a string and a reviver function.
The reviver function can transform each property and the final object before returning it.
If we want to change the prototype, we could place the logic into JSON.parse call
1 | var withPrinter = function (k, v) { |
You can even move this reviver function into the printer object itself
and even add object validation (I am using
check-types here):
1 | var printer = { |
In conclusion, objects received over the wire do not have to be just data,
we can quickly attach methods. In most environments (WebKit, nodejs) we only
point the prototype property at a given object. We even have a place to do this
in JSON.parse reviver argument.
Update
I found an interesting quirk in V8 JavaScript implementation. If you create a bare object, you cannot later set its prototype. The prototype is shown, but does not work! For example:
1 | var foo = Object.create(null); |
Weird, right?