My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.
Showing posts with label defineProperties. Show all posts
Showing posts with label defineProperties. Show all posts

Friday, November 25, 2011

On Complex Getters And Setters

A common use case for getters and setters is via scalar values rather than complex data.
Well, this is just a programmer mind limit since data we could set, or get, can be of course much more complex: here an example

function Person() {}
Person.prototype.toString = function () {
return this._name + " is " + this._age;
};
// the magic identity configuration object
Object.defineProperty(Person.prototype, "identity", {
set: function (identity) {
// do something meaningful
this._name = identity.name;
this._age = identity.age;
// store identity for the getter
this._identity = identity;
},
get: function () {
return this._identity;
}
});

With above pattern we can automagically update a Person instance name and age through a single identity assignment.

var me = new Person;
me.identity = {
name: "WebReflection",
age: 33
};

alert(me); // WebReflection is 33

While the example may not make much sense, the concept behind could be extended to any sort of property of any sort of class/instance/object.

What's Wrong

The problem is that the setter does something in order to keep the object updated in all its parts but the getter does nothing different than returning just the identity reference.
As summary, the problem is with the getter and the reason is simple: from user/coder perspective this may not make sense!

me.identity.name = "Andrea";

alert(me); // WebReflection is 33

In few words we are changing an object property, the one used as identity for the me variable, leaving the instance of Person untouched ... and semantically speaking this looks wrong!

A Better Approach

If the setter aim is to update a state there will be only a well known amount of properties to re-assign before this status can be updated.
In this example we would like to be sure that as soon as the identity has been set, the instance toString method will produce the expected result and without relying an external reference, the identity object itself.

// listOfNames and listOfAges are external Arrays
// with same length
for (var
identity = {},
population = [],
i = 0,
length = listOfNames.length;
i < length; ++i
) {
// reuse a single object to define identities
identity.name = listOfNames[i];
identity.age = listOfAges[i];
population[i] = new Person;
population[i].identity = identity;
// out of this loop we want that each
// instanceof Person prints out
// the right name and surname
}

// we cannot do it lazily in the toString method ...
// this will fail indeed
Person.prototype.toString = function () {
return this._identity.name + " is " + this.identity._age;
};

Got it? It's basically what happens when we use an object to define a property, more properties, or to create inheriting from another one: properties and values are parsed and assigned at that moment, not after!

var commonDefinition = {
enumerable: true,
writable: true,
configurable: false
};

var
name = (commonDefinition.value = "a"),
a = Object.defineProperty({}, "name", commonDefinition),
name = (commonDefinition.value = "b"),
b = Object.defineProperty({}, "name", commonDefinition)
;
alert([
a.name, // "a"
b.name // "b"
]);

As we can see if the property assignment would have been lazy the result of the latest alert would have been "b", "b" since the object used to define these properties has been recycled ... I hope you are still following ...

A Costy Solution

There is one approach we may consider in order to make this identity consistent ... the one that stores an identity with getters and setters.

// the magic identity configuration object
Object.defineProperty(Person.prototype, "identity", {
set: function (identity) {
var self = this;

// do something meaningful
self._name = identity.name;
self._age = identity.age;

// store identity as fresh new object with bound behavior
this._identity = Object.defineProperties({}, {
name: {
get: function () {
return self._name;
},
set: function (name) {
self._name = name;
}
},
age: {
get: function () {
return self._age;
},
set: function (age) {
self._age = age;
}
}
});
},
get: function () {
return this._identity;
}
});

What changed ? The fact that now we are able to pass through the instance and operate through the identity:


me.identity.name = "Andrea";

alert(me); // Andrea is 33

Cool hu ? ... well, it could be better ...

A Faster Solution

If for each Person and each identity we have to create a fresh new object plus at least 4 functions, 2 for getters and 2 for relative setters, our memory will fill up so quickly that we won't be able to define any other person identity soon and here comes the fun part: use the knowledge gained by this pattern internally!
Yes, what we could do to make things slightly better is to recycle the internal identity setter object definition in order to borrow maximum 4 functions rather than 4 extra per each Person instance ... sounds cool. uh?

function Person() {}
Person.prototype.toString = function () {
return this._name + " is " + this._age;
};

(function () {

var identityProperties = {
name: {
get: function () {
return this.reference._name;
},
set: function (name) {
this.reference._name = name;
},
configurable: true
},
age: {
get: function () {
return this.reference._age;
},
set: function (age) {
this.reference._age = age;
},
configurable: true
},
reference: {
value: null,
writable: true,
configurable: true
}
};

Object.defineProperty(Person.prototype, "identity", {
get: function () {
return this._identity;
},
set: function (identity) {
// something meaningful
this._name = identity.name;
this._age = identity.age;

// set the reference to the recycled object
identityProperties.reference.value = this;

// define the _identity property
if (!this._identity) {
Object.defineProperty(
this, "_identity", {value: {}}
);
}
Object.defineProperties(
this._identity,
identityProperties
);
}
});

}());

var
identity = {},
a = new Person,
b = new Person
;

identity.name = "a";
identity.age = 30;
a.identity = identity;

identity.name = "b";
identity.age = 31;
b.identity = identity;

alert([
a, // "a is 30"
b // "b is 31"
]);

So what we have there? A lazy _identity definition, good for those scenario where some getter or setter may never been invoked, plus a smart recycled property definition through a single object descriptor, and performances boosted up N times per each instance since no multiple different functions are assigned per identity properties getters and setters and no extra objects are created runtime ... arf, arf .. are you still with me ?

As Summary

Some JS developer keep asking for standard ways to do these kind of crazy stuff without realizing that few other programming languages are that flexible as JavaScript is ... it's maybe not that simple to find better, optimized, in therms of both memory consumption and raw performances, patterns to cover weird scenario but what we should appreciate is that with JS we almost have, always, a way to simulate something we did not even think about until the day before.
Have fun with JS ;)

Saturday, October 15, 2011

Object.prototype.define Proposal

Somebody may think that defineProperties is boring and I kinda agree on that.

The good news is that JavaScript is flexible enough to let you decide how to do that ... and here I am with a simple proposal that does not hurt, but can make life easier and more intuitive in modern JS environments.

Unobtrusive Object.prototype.define




How To

Well, the handy way you expect.
The method returns the object itself, so it is possible to define one or more property runtime and chain different kind of definitions, as example splitting properties from method and protected properties from protected methods.

var o = {}.define("test", "OK");
o.test; // OK

Multiple properties can share same defaults:

var o = {}.define(["name", "_name"], "unknown");
o.a; // unknown
o._a; // unknown

Methods are immutable by default and properties or methods prefixed with an underscore are by default not enumerable.

function Person() {}
Person.protoype.define(
["getName", "setName", "_name"],
[
function getName() {
return this._name;
},
function setName(_name) {
this._name = _name;
},
"unknown"
]
);

// by convention, _name property is not enumerable

var me = new Person;
me.getName(); // unknown

me.setName("WebReflection");
me.getName(); // WebReflection

for (var key in me) {
if (key === "_name") {
throw "this should never happen";
}
}


Last, but not least, if the descriptor is an object you decide how to configure the property.

var iDecide = {}.define("whatIsIt", {
value:"it does not matter",
enumerable: false
});
for (var key in iDecide) {
if (key === whatIsIt) {
throw "this should never happen";
}
}


100% Unit Test Code Coverage

Not such difficult task for such tiny proposal.
This test simply demonstrates the proposal works in all possible meant ways.

As Summary

We can always find a better way to do boring things, this is why frameworks, in all sizes and purposes, are great to both use or create. Have fun

Saturday, January 30, 2010

[ES5] Classes As Descriptor Objects

In my latest posts I have talked about current situation for JavaScript "Classes". In Good Old And Common JS Errors I have introduced a misconception of a generic "Class" function which is usually not able to produce instanceof Class, considering Classes in JS are functions indeed.
In Better JS Classes I have explained how to write a meaningful Class factory, avoiding the _super pollution over each extended method, while in the JS _super Bullshit I have explained why the _super/parent concept does not scale with JavaScript prototypal inheritance model.

More than a dev, Mr Crockford included, agreed that in JavaScript the classical pattern does not fit/scale/produce expected results, and even worst, it could cause "disasters" during a session and slow down overall performances (and about this topic I have already said that web developers should stop to test their stuff with powerful CPU, read Macs!
Buy a bloody Atom based device as I have done and after that we can talk about the meaning of performances, right? Netbook should be able to surf without 100% of CPU usage, do you agree?)

The best part over all these years of wrong classical inheritance implementation is that with JavaScript.Next, aka ES5, the way we can define instances does not even require a function definition. Wanna know more?

Objects Extend Objects

It's that simple, we all know this is what happens in OOP Languages based over prototypal inheritance ... and since it is that simple ...

ES5 Classes As Descriptors

Or if you prefer, as definition objects. Yep! While every framework is using functions to create instances via new, in ES5 we could completely forget this pattern.

// class descriptor/definition
var MyClass = {
getValue:function () {
return this._value;
},
setValue:function (_value) {
this._value = _value;
}
};

// MyClass instance
var mc = Object.create(

// inherits from MyClass
MyClass,

// define privileged properties or methods
{
_value:{
value:"Hello ES5"
}
}
);

// Hello ES5
alert(mc.getValue());

Pretty cool, isn't it, but I am sure somebody is already arguing something like: "... and what about instanceof?"

instanceof

The instanceof operator checks if an object inherits from the implicit constructor prototype.
A common mistake is to think that instanceof is related to the function itself while it has nothing to do with it.

function A() {};
var klass = {};
A.prototype = klass;

var a = new A;

// true
alert(a instanceof A);

A.prototype = {};

// false
alert(a instanceof A);

function B(){};
B.prototype = klass;

// true
alert(a instanceof B);

Is it clear? instanceof works only with functions and only with those functions with an implicit prototype property, the default one, or specified as a generic object.

function A() {};
A.prototype = null;

var a = new A;

// throws: 'prototype' property of A is not an object
alert(a instanceof A);

To avoid above error, being JavaScript 99% dynamic, we could use a safer check, and for this example via isPrototypeOf:


function instanceOf(o, F) {
return !!F.prototype && F.prototype.isPrototypeOf(o);
};

function A() {};
var a = new A;

// true
alert(instanceOf(a, A));

Boring, slow, etc etc ... why don't we use directly our initial class description to understand if an instance is inheriting that class?

// using first example code
alert(MyClass.isPrototypeOf(mc));
// true

As alternative, we could use the global Object.getPrototypeOf method:

Object.getPrototypeOf(mc) === MyClass;

To be honest, for both easier scope resolution and semantic, I prefer the isPrototypeOf way. Furthermore, specially if the chain is deeper than 1 level, getPrototypeOf could generate false negatives while getPrototypeOf won't.

function A() {};
function B() {};
(B.prototype = new A).constructor = B;

var b = new B;

// true
alert(Object.getPrototypeOf(b) === B.prototype);

// false
alert(Object.getPrototypeOf(b) === A.prototype);

// both TRUE!
alert(B.prototype.isPrototypeOf(b));
alert(A.prototype.isPrototypeOf(b));

Accordingly, the best function to emulate an instanceOf function over objects or "classes" could be:


function inherits(o, __proto__) {
// (C) Mit Style WebReflection suggestion
return ((
typeof __proto__ === "function" ?
__proto__.prototype :
__proto__
) || {}).isPrototypeOf(o);
};

// test case
function A() {};
function B() {};
(B.prototype = new A).constructor = B;

var b = new B;

// true,true,true,true
alert([
inherits(b, A),
inherits(b, B),
inherits(b, A.prototype),
inherits(b, B.prototype)
]);


More ES5 Friendly Patterns

If we use what ES5 is bringing into JavaScript and via native execution speed, we may be interested into more "articulated" patterns to define "classes", or simply classes instances.

Object.defineProperties

First of all, as I have twitted already, please ignore that wrong suggestion about custom implementation.
The only place where all those checks could make sense is inside Object.defineProperty, and not twice in both defineProperties AND definePrperty, since if latter exists, why on earth we should try to emulate its internal checks?
If defineProperty does NOT exists, why shoud we try to emulate it without checks? I hope MDC guys will remove that nonsense from that page, I'd love to be sure developers get ES5 properly, good practices included.
Back in the topic, here there is an example:

function A(_value) {
// implicit init method
// where we define privileged proeprties/methods
Object.defineProperties(this, {
_value:{
value:_value,
// as example since it is false by default
enumerable:false
}
});

// and eventually we perform some task
};

// Class definition
Object.defineProperties(A.prototype, {
toString:{
value:function () {
return "" + this._value;
},
configurable:false
}
});

var a = new A(123);
alert(a); // 123

Does it make sense? We have more power via defineProperties and we can reuse, or share, objects across the whole library without any kind of problem.
One thing we should be ware about, is that A.prototype is always re-assignable, so if we try to define the A prototype property itself as non configurable we won't be safer anyway, it can be overwritten!

Dual Behavior: Factory Emulator


// when use strict will be enabled
// to obtain dual behavior (factory/constructor)
function A(_value) {
"use strict";
return Object.defineProperties(this || new A(_value), {
//... privileged definition
});
};


Dual Behavior Via Object.create


// Object.create way
function A(_value) {
// note that "this" may be useless, A() same of new A()
// inheritance chained in place
return Object.create(A.prototype, {
_value:{
value:_value,
enumerable:false
}
});
};

A.prototype = Object.create(A.prototype, {
toString:{
value:function () {
return "" + this._value;
},
configurable:false
}
});


Confused ???

I know I have started saying that theoretically we don't need anymore a single function to implement inheritance via ES5, but I bet those Java guys won't ever accept the fact JavaScript has no Classes and this is why I have suggested different patterns so that everybody could be happy about these ES5 features.

When Can We Start To Use These Features

Right now, in my opinion, including just one single vice versa project Object.js file, being careful about the target browser.
Unfortunately, and as usual, Internet Explorer is behind every other browser and some method cannot be perfectly emulated.
This is also why I have decided to show both defineProperties and create way, since IE defineProperties works only with DOM prototypes and global Window (or generally speaking only with natives and not with object) so that create could be used without problems, so far avoiding get/set and considering that configuration properties may not be respected.
I know this is crap, but until IE9 or the day we'll finally decide to drop this browser support, there's not that much we can do: annoying!

P.S. to test all these natively, we can use a WebKit nightly build.