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

Wednesday, October 19, 2011

Do You Really Know Object.defineProperty ?

I am talking about enumerable, configurable, and writable properties of a generic property descriptor.

enumerable

most likely the only one we all expect: if false, a classic for/in loop will not expose the property, otherwise it will. enumerable is false by default.

writable

just a bit more tricky than we think. Nowadays, if a property is defined as non writable, no error will occur the moment we'll try to change this property:

var o = {};
Object.defineProperty(o, "test", {
writable: false,
value: 123
});
o.test; // 123
o.test = 456; // no error at all
o.test; // 123

So the property is not writable but nothing happens unless we try to redefine that property.

Object.defineProperty(o, "test", {
writable: false,
value: 456
});
// throws
// Attempting to change value of a readonly property.

Got it ? Every time we would like to set a property of an unknown object, or one shared in an environment we don't trust, either we use a try/catch plus double check, or we must be sure that Object.getOwnPropertyDescriptor(o, "test").writable is true.
writable is false by default too.

configurable

This is the wicked one ... what would you expect from configurable ?
  • I cannot set a different type of value
  • I cannot re-configure the descriptor
Fail in both cases since things are a bit different on real world. Take this example:

var o = Object.defineProperty({}, "test", {
enumerable: false,
writable: true,
configurable: false, // note, it's false
value: 123
});

Do you think this would be possible ?

Object.defineProperty(o, "test", {
enumerable: false,
writable: false, // note, this is false only now
configurable: false,
value: "456" // note, type and value is different
});

// did I re-configure it ?
o.test === "456"; // true !!!

Good, so a variable that is writable can be reconfigured on writable attribute and on its type.
The only attribute that cannot be changed, once flagged as configurable and bear in mind that false is the default, is configurable itself plus enumerable.
Also writable is false by default.
This inconsistency about configurable seems to be pretty much cross platform and probably meant ... why ?

Brainstorming

If I can't change the value the descriptor must be configurable at least on writable property ... no wait, if I can set the value as not writable then configurable should be set as false otherwise it will loose its own meaning ... no, wait ...

How It Is

writable is the exception that confirms the rule. If true, writable can always be configurable while if false, writable becomes automatically not configurable and the same is true for both get and set properties ... these acts as writable: false no matters how configurble is set.

How Is It If We Do Not Define


// simple object
var o = {};

// simple assignment
o.test = 123;

// equivalent in Object.defineProperty world
Object.defineProperty(o, "test", {
configurable: true,
writable: true,
enumerable: true,
value: 123
});

Thanks to @jdalton to point few hints out.

As Summary

The configurable property works as expected with configurable itself and only with enumerable one.
If we think that writable has anything to do with both of them we are wrong ... at least now we know.

Wednesday, March 16, 2011

Object.defineHybridProperty

Update Yes, I did it: getters and setters for IE < 9 and other browsers


After my early Hooorrayyyy! about compatible IE < 9 getters and setters, I have been experimenting a bit more on how to solve the JSObject to VBVariant and vice-versa assignment and the result was an horrendous monster loads of potential memory leaks and performances implications for the already slow bounce of browsers such IE8, 7, and 6.
Since limitations were also too many, as described in this even earlier attempt from dojo framework, I have realized that VBScript was simply a no-go, or better, probably the wrong answer to the question: how can I have cross-browser getters and setters?

The jQuery Hybrid Answer

Back in 2009, James Padolsey described the jQuery framework as a sort of getters/setters simulator API, comparing the semantic and beauty of non-standard Spidermonkey __defineGetter__ and __defineSetter__, against jQuery coding style, where many "methods" could be considered as getters or setters.

// get the innerHTML of the first node found through the selector
$("#selector").html();

// set the innerHTML of the first(?) node found through the selector
$("#selector").html("
");

As showed above, the jQuery().html method can be considered a friendly answer for a cross browser get/set implementation, and surely much more friendly than what antimatter15 proposed some time ago (Pssss! dude, I could not find your real name in the "About" section ...).

My "due jQuery success, why not!" Proposal

In ES5 everything is so natural and simple, Object.defineProperty and Object.defineProperties work like a charm and IE9 with all other browsers as well. We may decide to use good old __defineGetter/Setter__ as fallback for older Opera, Chrome, Safari, or Firefox, but not for IE since these methods are not supported at all.
However, even using these fallbacks, the descriptor object will result inconsistent because writable, enumerable, and configurable properties won't act as expected.
At this point I have decided to fallback into a "jQuery approach" solution that will behave exactly the same in all old and newer browsers, being still able to use an ES5 like descriptor that in a not that far away future won't even need to be changed at all, it will simply work.

A generic Person.prototype Descriptor



var personDescriptor = {
// requires council notification on change
name: {
get: function () {
return this._name;
},
set: function (name) {
// notify here the council about this change
this._name = name;
}
},
// this property is unfortunately immutable (via public access)
age: {
get: function () {
return this._age;
}
},
// simply a method that occurs once per year
birthday: {
value: function () {
this._age++;
}
}
};


Current ES5 Person "Class" Example



//* ES5 example
function Person(name, age) {
// a new Person in town
this._age = age || 0; // default, just born
this.name = name;
}

Object.defineProperties(Person.prototype, personDescriptor);

var me = new Person("Andrea", 32);
// will throw an error
// me.age = 20;
alert([me.name, me.age]); // Andrea, 32
me.birthday();
alert(me.age); // 33
//*/


Current ES3 Person "Class" Example



//* ES3 example
Person = function Person(name, age) {

// a new Person in town
this._age = age || 0; // default, just born

// we still want to notify the council,
// no direct this._name set
this.name(name);

}

Object.defineHybridProperties(Person.prototype, personDescriptor);

var me = new Person("Andrea", 32);
// nothing will happen
// me.age(20);
alert([me.name(), me.age()]); // Andrea, 32
me.birthday();
alert(me.age()); // 33

// wanna know what they are?
alert(me.name);
alert(me.age);
alert(me.birthday);
//*/

The source code of Object.defineHybridProperty, together with Object.defineHybridProperties, is here and as you can see it's a quite simple and compact piece of code.

Use Cases and DONTS

The function name should be explicit enough, and it's used to define hybrid properties.
Hybrid properties could be confused with methods ... and actually, all hybrid properties that have a get and/or set descriptor, become de-facto an object methods with current constrain: zero arguments to get, 1 single argument to set.
Use cases have been already described, a jQuery like API could use without problems the current approach, making the future ES5 only refactory less painful than whatever other get/set approach.
Indeed, once we know which descriptor is using getters/setters, all we have to do is to remember which property has been made hybrid, and change accordingly each invoke with arguments as assignment, and removing brackets from every other empty invoke.
Please Note I will update later the code in order to properly assign Object.prototype native names such toString so that IE browsers will consider them as well.
Done, it's updated and 409 bytes minified and gzipped, have fun :)

Friday, August 20, 2010

Object.defineProperty ... but Strict!

In my precedent post entitled A Pascal record via JavaScript I have showed a basic function able to emulate type hints behavior via JavaScript.
Even if that was a proof of concept, I consider other languages simulation of unsupported features an error, first of all because the behavior will rarely be exactly the expected one, secondly because our curent programming language may already have something similar to better learn and use.

A new ES5( direction )

As soon as I have written the Pascal example, I have realized that the good "old" Object.defineProperty, implemented in all major browsers (IE < 9 sucks, you know that ...), has basically the same meaning: define object accessors.

The only missing part, intrinsic in the JavaScript nature, is the property type, where this type could be eventually used for arguments or returns checks when the property is a method.

My Early Attempts

Back in May 2007, my JavaStrict experiment was already trying to do something similar, something implemented after 2 years even in dojo framework as well.
This may tell us that "somebody" would really like to put this kind of checks on daily code, even if performances cannot obviously be the best possible one.

Production VS Deployment

Keeping always in mind that performances matter, the new little monster I am going to show here can be flagged with a boolean, nothing different from:

Object.defineStrictProperty.production = true;

Once in place, the whole logic will switch from "strict type" to "free type" and the used function will be the native Object.defineProperty respecting basically the same behavior.

Object.defineStrictProperty / ies

I have put the code in devpro.it, while I am going to show how it works here.

How to declare a type

Next piece of code is the most basic usage of this method.

var myObject = Object.defineStrictProperty({}, "name", {
type: "string"
});

myObject.name = "it's a me!";

alert(myObject.name);

myObject.name = 123;

// throw new TypeError("wrong type");
//
// console.log =>
// Object
// > expected: "string"
// > received: 123



The meaning of "type"

The type property can contain any of these values:
  • string, where the check will be performed against the typeof value (e.g. "string", "number", "boolean", etc)

  • function, where the check will be performed against the value instanceof type (e.g. Function, Object, Array, Date, etc)

  • object, where the check will be performed against the type.isPrototypeOf(value) (e.g. Array.prototype, others ...)

The only exceptions are null and undefined.

How to declare a returned value

If the property type is "function" or Function there are other extra properties we could define such returns and arguments. Here there is a returns example:

var myObject = Object.defineStrictProperties({}, {
name: {
type: "string",
value: "it's a me!"
},
toString: {
type: "function",
returns: "string",
value: function () {
return this.name;
}
}
});

alert(myObject); // it's a me!

myObject.toString = function () {
return 456;
};

alert(myObject);

// throw new TypeError("wrong return");
//
// console.log =>
// Object
// > expected: ["string"]
// > received: 456


Multiple returns

Since JavaScript is dynamic, there is nothing wrong, logic a part, into different returns. To make it possible we simply need to specify an array with all expected types.

// precedent code with this difference
...
toString: {
type: "function",
returns: ["string", "number"],
value: function () {
return this.name;
}
}

// precedent example won't fail now


How to declare expected arguments

Similar returns concept applied to arguments, so that we can specify a single argument type, a list of arguments type, a list of arguments. Here the difference:

...
setName: {
type: "function",
// checks if a single argument
// with typeof string has been sent
arguments: "string",
// same as above
arguments: ["string"],
// checks if two arguments
// with typeof string and number
// has been sent, order matters!
arguments: ["string", "number"],
// checks if one argument
// with typeof string OR number
// has been sent
arguments: [["string"], ["number"]],
value: function (name) {
this.name = name;
}
}
...

The number of arguments and relative overload per call is arbitrary. As example, we could have a method defined with these arguments without problems.

// generic object for Ajax calls
...
get: {
type: "function",
returns: "string",
arguments: [["string"], ["string", "string", "string"]],
value: function (uri, user, pass) {
this.open("get", uri, false, pass && user, pass);
this.send(null);
return this.responseText;
}
}
...

With a simple logic like that we can "enforce" the program to send one argument or three when, if three, all of them will be checked as strings.

Unlimited arguments

We may decide that the first argument is the only one we would like to check. Well, in this case we can simply create a group of allowed types and ignore the rest.
The arguments logic should be flexible enough due JavaScript nature where overloads can only be simulated via number or tye of arguments checks.

Getters and Setters

The whole logic behind is based on get/set property descriptor, where a nice fallback to __defineGetter__ or __defineSetter__ will be used when Object.defineProperty is missing.
This means that we can describe properties the same way we could do via Object.defineProperty, using enumerable and others as well to make the whole code gracefully degradable when the production flag is set to true.

As Summary

Unless developers won't be interested into this approach, I think this method is basically complete as is and we can use it at own risk.
The whole script stays in about 870 bytes minified and gzipped, not a big deal for what it offers.
In any case, I am looking forward for your feedback!

Wednesday, April 28, 2010

Object.defineProperty - A Missed Opportunity

Just a quick post about some clever hack we should probably forget ... make old scripts less obtrusive using new ES5 features.
I am talking bout those guys out there that use scripts with a classic:

onload = function () { ... };
// or
this.onload = ...
// or
window.onload ...
// or
self.onload ...
// etc etc


Apparently WebKit Nightly fires an error when we try to define getters and setters via Object.defineProperty and this is already enough to remove that "hoooraayyyy" for my silly test .... here the code:

Object.defineProperty(this, "onload", (function (self, callback) {
function onload(e) {
while (callback.length) {
callback.shift().call(self, e);
}
}
self.addEventListener ?
self.addEventListener("load", onload, false) :
self.attachEvent("onload", onload)
;
return {
get: function () {
return onload;
},
set: function (onload) {
callback.push(onload);
}
};
}(this, [])));


If we put above snippet before any other script, we can be almost sure that nobody will be able to break anything with classic obtrusive operations:

// test above concept via Chrome and maybe IE8 or others
onload = function () {
alert(1);
};

this.onload = function () {
alert(2);
};


That's it, something that may have been good will probably be just a proof of concept :-)

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.

Monday, March 23, 2009

[IE8] Global constants via defineProperty ... Illusion!

I do not want to spend a single word about Internet Explorer 8 Object.defineProperty implementation, which works only with DOM prototypes and the super global window but not with user defined objects, as __defineGetter__ and __defineSetter__ do since ages:
Standards are an important factor to ensure browser interoperability for the Web developer (n.d. oh, really?!??!?!!!!). The accessor property syntax has only recently begun standardization (you guys have a weird concept of the time ... or the meaning of "recent" in IT therms ...). As such, many browsers support an older, legacy syntax ... (n.d. which at least has dignity to work with every kind of object ... )


Anyway, few things are better than nothing, so welcome to Object.defineProperty!

Let IE8 behaves like every other or change every other to respect new M$ standard?

This was the first question when I thought about this new global function: does it mean we finally have a way to emulate __defineGetter__ and setter in Internet Explorer as well? First of all: NO, since IE implementation works only with DOM and window, secondly, we need to add an Object.prototype method (to be honest ... two) and we all know that Object.prototype is the "untouched one".
So, let's do in the other way, we can add a method to the global function Object, which is less obtrusive and more compatible ^_^;;

if(!Object.defineProperty && Object.prototype.__defineGetter__)
// a silly WebReflection idea
Object.defineProperty = function(obj, prop, getset){
if(getset.get)
obj.__defineGetter__(prop, getset.get);
if(getset.set)
obj.__defineSetter__(prop, getset.set);
};

Well done, now we should simply parse every passed obj to understand if those are IE8 compatible ... is it worthy? ... dunno yet, just for fun, destroy IE8 environment with this evil Object.prototype if you want:

if(!Object.prototype.__defineGetter__ && Object.defineProperty){
// the second silly WebReflection idea!
Object.prototype.__defineGetter__ = function(prop, get){
Object.defineProperty(this, prop, {get:get});
};
Object.prototype.__defineSetter__ = function(prop, set){
Object.defineProperty(this, prop, {set:set});
};
};

Same problem, we cannot use a method with every browser ... the only good part of this global Object method is described in this msdn page, an addEventListener, finally, for IE8 too and every node without usage of wrappers (usually the $ object ...)


The insane idea: Global Constants for EveryBody?

... oooOOOOOOOO Crap! Even if we can do something like this:

// create the evil plan!
Object.defineProperty(
window,
"nobodyCanChangeMe",
{
get:function(){
return "I am like a constant!";
},
set:function(){
throw new Error(
"You cannot change a constant, don't ya know it?"
);
}
}
);

// test the evil plan! (muHaHAHAHA in the background ...)
nobodyCanChangeMe = 123;
// Error: You cannot change a constant, don't ya know it?

You will probably be (un)surprised to know that Internet Explorer 8 (tada, tada, taDAAA!!!) completely ignore the setter whenever you decide to redefine the property!!!

// above perfect evil plan code ... plu ...

Object.defineProperty(window, "nobodyCanChangeMe", {get:function(){
return "you must be joke!";
}});
alert(nobodyCanChangeMe); // you must be joke!


As Summary

VBScript via browser supports constants since dunno how many years. I personally wrote a PHP define like functions ages ago but nothing, Microsoft decided to release IE8 without constants, which are supported almost by evey other (FireFox, Chrome, Safari, Opera -- buggy/meaningless in the latter case) so there is no way to protect our code for JsonP requestes, safe evaluations, whatever you would like to be "99%" sure nobody can change, etc etc ... so this is a thank to IE Team, they worked hard, but this is also a: was it that difficult to put more effort in the JavaScript engine, rather than scam the world with those useless page loading benchmarks?

Enjoy the new Web 2.IE8 Era!