My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

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


(function (Object) {
/**
* @name Object.prototype.define
* @description A simplified and intuitive way to define objects properties.
* @requires Object.defineProperty, Array.prototype.forEach (native or shimmed)
* @author Andrea Giammarchi (@WebReflection)
* @license Mit Style
*/
// function reused for every defined property
function assign(key, i, descriptor) {
// if descriptor is not present
// last valid one is recycled
// handy to define multiple properties
// with the same type
if (i in this.d) {
last = i;
}
descriptor = this.d[last];
// if descriptor is not an object
if (typeof descriptor != "object" || descriptor == null) {
// the descriptor is created runtime
descriptor = {
value: descriptor
};
}
// by default defined properties should not be redefined
// otherwise there could be unexpected results
if (!hasOwnProperty.call(descriptor, "configurable")) {
descriptor.configurable = false;
}
// by default properties are writeable
// unless the property is not a method
// so that methods cannot be changed runtime
if (
!hasOwnProperty.call(descriptor, "writable") &&
!(
hasOwnProperty.call(descriptor, "set") ||
hasOwnProperty.call(descriptor, "get")
)
) {
descriptor.writable = typeof descriptor.value != "function";
}
// by convention all property names that starts with underscore
// are considered not enumerable
if (!hasOwnProperty.call(descriptor, "enumerable")) {
descriptor.enumerable = key.charAt(0) != "_";
}
// we can define now the property after all handy checks
defineProperty(this.r, key, descriptor);
}
var // private shortcuts shared across this scope
defineProperty = Object.defineProperty,
hasOwnProperty = {}.hasOwnProperty,
a = [],
concat = a.concat,
last = 0,
define = "define"
;
// if not defined already ...
define in Object.prototype || defineProperty(
// set the method and do not let others change it
Object.prototype,
define,
{
enumerable: false,
configurable: false,
writable: false,
value: function defining(
keys, // String or Array of Strings
descriptors // Object or Array of Objects
) {
concat.call(a, keys).forEach(
assign,
{
// reference
r: this,
// descriptors
d: concat.call(a, descriptors)
}
);
last = 0;
return this;
}
}
);
}(Object));


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

No comments: