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

Saturday, August 20, 2011

Overloading the in operator

In all its "sillyness", the CoffeeShit project gave me a hint about the possibilities of an overloaded in operator.

The Cross Language Ambiguity

In JavaScript, the in operator checks if a property is present where the property is the name rather than its value.

"name" in {name:"WebReflection"}; // true

However, I bet at least once in our JS programming life we have done something like this, expecting a true rather than false.

4 in [3, 4, 5]; // false
// Array [3, 4, 5] has no *index* 4


The Python Way

In Python, as example, last operation is perfectly valid!

"b" in ("a", "b", "c") #True
4 in [3, 4, 5] # True
The behavior of Python in operator is indeed more friendly in certain situations.

value in VS value.in()

What if we pollute the Object.prototype with an in method that is not enumerable and sealed? No for/in loops problems, neither cross browsers issues since if it's possible in our target environment, we do it, otherwise we don't do it ... a fair compromise?


Rules

  • if the target object is an Array, check if value is contained in the Array ( equivalent of -1 < target.indexOf(value) )

  • if the target object is an Object, check if value is contained in one of its properties ( equivalent of for/in { if(object[key] === value) return true; } ).
    I don't think nested objects should be checked as well and right now these are not ( same as native Array#indexOf ... if it's an array of arrays internal arrays values are ignored and that's how it should be for consistency reason )

  • if the target object is a typeof "string", check if value is a subset of the target ( equivalent of -1 < target.indexOf(value) ).
    The Python logic on empty string is preserved since in JavaScript "whateverStringEvenEmpty".indexOf("") is always 0

  • if the target property is a typeof "number", check if value is a divisor of the target ( as example, (3).in(15) === true since 15 can be divided by 3)
I didn't came out with other "sugarish cases" but feel free to propose some.

Compatibility

The "curious fact" is that in ES5 there is no restrictions on an IdentifierName.
This is indeed different from an Identifier, where in latter ReservedWord is not allowed.
"... bla, bla bla ..." ... right, the human friendly version of what I've just said is that obj.in, obj.class, obj.for etc etc are all accepted identifiers names.
Accordingly, to understand if the current browser is ES5 specs compliant we could do something like this:

try {
var ES5 = !!Function("[].try");
} catch(ES5) {
ES5 = !ES5;
}

alert(ES5); // true or false
Back in topic, IE9 and updated Chrome, Firefox, Webkit, or Safari are all compatible with this syntax.

New Possibilities

Do you like Ruby syntax?

// Ruby like instances creation (safe version)
Function.prototype.new = function (
anonymous, // recycled function
instance, // created instance
result // did you know if you
// use "new function"
// and "function" returns
// an object the created
// instance is lost
// and RAM/CPU polluted
// for no reason?
// don't "new" if not necessary!
) {
return function factory() {

// assign prototype
anonymous.prototype = this.prototype;

// create the instance inheriting prototype
instance = new anonymous;

// call the constructor
result = this.apply(instance, arguments);

// if constructor returned an object
return typeof result == "object" ?
// return it or return instance
// if result is null
result || instance
:
// return instance
// in all other cases
instance
;
};
}(function(){});

// example
function Person(name) {
this.name = name;
}

var me = Person.new("WebReflection");
alert([
me instanceof Person, // true
me.name === "WebReflection" // true
].join("\n"));

Details on bad usage of new a part, this is actually how I would use this method to boost up performances.

// Ruby like instances creation (fast version)
Function.prototype.new = function (anonymous, instance) {
return function factory() {
anonymous.prototype = this.prototype;
instance = new anonymous;
this.apply(instance, arguments);
return instance;
};
}(function(){});

cool?

Do Not Pollute Native Prototypes

It does not matter how cool and "how much it makes sense", it is always considered a bad practices to pollute global, native, constructors prototypes.
However, since in ES5 we have new possibilities, I would say that if everybody agrees on some specific case ... why not?
for/in loops can now be safe and some ReservedWord can be the most semantic name to represent a procedure as demonstrated in this post.
Another quick example?

// after Function.prototype.new
// after Person function declaration
Object.defineProperty(Object.prototype, "class", {
enumerable: !1,
configurable: !1,
get: function () {
return this.__proto__.constructor;
}
});

var me = Person.new("WebReflection");

// create instance out of an instance
var you = me.class.new("Developer");

alert([
you instanceof Person, // true
you.name === "Developer" // true
].join("\n"));

I can already see Prototype3000 farmework coming out with all these magic tricks in place :D
Have fun with ES5 ;)

7 comments:

sasklacz said...

I really miss that Python's functionality in JS :)

Nikke said...

I really wish you'd take a loot at your layout in Chrome. I hate that I need to open up another browser just to read this blog.

Screenshot: http://i5.aijaa.com/b/00627/8541731.jpg

Andrea Giammarchi said...

I don't have any problem but styles are from the old version of blogger, I did not tough them

Nikke said...

Good ole Works On My Computer -syndrome.

Nikke said...

If it helps, for some reason everything is scaled down by 50%. I've been seeing this for quite a while now, and I'm quite sure I've also seen someone else mention this. I'm using the latest Chrome (not Chromium).

Andrea Giammarchi said...

are you sure is not that you pressed by mistake zoom out ? Is everything ok with zoom 100% ? ...

Nikke said...

Oh, that was embarrassing. Sorry for the trouble. As usual, it was just another case of PEBKAC.

But in my defense, never before have I seen a (browser) zoom state persist like this.