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?