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

Friday, December 04, 2009

with: Some Good Example

OK, I have said my last post would have been the last one about the with statement ... well, before I close the argument, I would like to show a couple of common good examples.

Solve References Problem

This is what somebody defined
a tiny masterpiece


with({o:myreference}){
o.doStuff();
o.var1 = "whatever";
// etc etc ...
};

Above example annihilate every blame about the not sure if that is the var I meant to use since there is an explicit reference as is, as example, in Python programming language (until version 3.11)

// Python
with open("x.txt") as f:
data = f.read()

// JavaScript equivalent
with({f:/* as */open("x.txt")})
data = f.read()


Memory Safe Operations

Another recycled example before I show more interesting stuff ... apparently the whole problem is about write silly code inside with, as if everybody has to define variables or assign stuff inside this statement. Hilariously, I have basically never assigned anything inside a with in 8 years of ECMAScript programming ... does it tell you anything?
A clever usage could be the one to beat whatever compressor and make code less redundant:

with(document)
with(documentElement)
insertBefore(
createElement("script"),
firstChild
)
.text = "alert(1)"
;

Try to do more in less characters considering I am not creating a single reference at all, as I am not creating a useless closure just to justify references creation ... done? Now use whatever compiler/compresor/minifier you want and try to obtain less than 99 bytes, respecting the same cross browser, clean, memory safe and leaks free nature ...

Discover Named Arguments

A too much common approach in JavaScript is the one to create an inline executed closure to perform some task being sure local variables won't disturb outer or global scope.

(function(obj, collection, callback){
// a common argument normalizer
if(!collection)
collection = []
;
// a loop, 'cause we don't want to perform it
// in the global scope
for(var i = 0, length = collection.length, tmp; i < length; ++i){
// callback returns an object only under certain conditions
tmp = callback.call(obj, collection[i]);
if(tmp){
tmp.doStuff();
collection[i] = tmp;
};
};
})();

Guys, above example is daily basis JavaScript programming
  1. create a closure to avoid outer scope properties declaration

  2. normalize, if necessary, unexpected/undefined/empty arguments

  3. define internal closure variables with or without a value

  4. perform some operation

  5. exit from the executed closure (implicit)
Now, consider we all love to call methods or functions via configuration objects

ajax({
url:somestring,
params:someObject,
success:callback,
failure:shenanigans
});

A configuration object is semantic, it allows us to perform some task inline, like ternary operator assignment (and I bet since somebody does not know ternary operator ES5 team will decide it's evil ...) and make callback calls more friendly ... but what we have always been envious about Python is the ability to use named arguments defining defaults, if necessary, for each value.
In JavaScript we can send named arguments, via a configuration object, but we cannot define arguments defaults ... but in one shot, we could emulate everything defining both defaults and, since we need to define them in any case, local variables ... don't you follow me?

// zero closure named arguments example
with({
obj:myObject,
// inline defaults, if necessary
collection:collection || [],
callback:myCaseAnalyzer,
// local variables
i:0,
length:collection ? collection.length : 0,
// local undefined variable
tmp:null
}){
for(;i < length; ++i){
tmp = callback.call(obj, collection[i]);
if(tmp){
tmp.doStuff();
collection[i] = tmp;
};
};
};

Same behavior, zero ambiguity, if we call another variable name is because we need that variable from the outer scope. If we create a variable we are doing wrong because inside a closure we would have used var, while with this approach we need simply to define, rather than var, a property with null value.
Moreover, if tomorrow we need an extra argument via with statement all we need to do is to define it in whatever position we like, without being worried about arguments order (let's say bad design) since there is no order, except the one we prefer, in object properties.

Global Clear Ajax Call

This is the last example for this post, just ajax when we need it, whenever we are, without the classic reference and the missed "this" feature in the onreadystatechange function:

with(this.XMLHttpRequest ?
new XMLHttpRequest :
new ActiveXObject("Microsoft.XMLHTTP")
){
open("get", "?ajax=true", true);
onreadystatechange = function(){
if(readyState === 4)
// do stuff with the responseText/XML
alert(responseText)
;
};
send(null);
};


Global Paradox

The funniest part ever about this with statement is that basically every JavaScript has an implicit global object scope.
In few words every time a script is executed is basically the same of a massive with

with(window){
// same as window.alert(123);
alert(123);

// same as window.onload = function(){};
onload = function(){
};

(function(){
// same as new window.XMLHttpRequest
var xhr = new XMLHttpRequest;
})();

// same as window.String.fromCharCode
String.fromCharCode(1);
}

Of course being the global object the last possible outerscope, if we define a variable directly or via var in that scope we will attach this property to the window one.
In few words the with is the ABC of JavaScript but again, some clever guy decided that since somebody cannot understand closures, then extended closures, the basic principle of the programming language itself has to be removed ... right?

Think about it, if ES5 will remove even the with statement making this unavailable in the "use strict" future proof declaration, JavaScript won't be the one that created all its success around its closure and prototype based nature.

Think!

18 comments:

Kamil Trebunia said...

These are really entertaining snippets of code (and a very good read), but honestly, do you use any of those techniques on a daily basis?

Andrea Giammarchi said...

the side effect of the statement is the constant scope resolution, less performances, missed recursion, unless we don't implement a goto label on the top, and portability, since it strongly depends on the current inner and outer scope.
For an inline snippet I may use the statement, e.g. DOM operations, but for performances and frameworks purposes I cannot use it that much.
I bet that if with statement was somehow faster than other procedures, more people would have used it.
My whole point is that this is a feature few people know or use in a safe/powerful way, and I do not agree at all with the argument:
"Since juniors mess up with the statement, we remove the statement".
I mean, if this is ES5 way, I won't be surprised if ES5 will make JavaScript the real toy everybody thinks it is.
They have removed callee, and caller, because while every other programming language has implemented Reflection, ES5 removed JavaScript introspection, hilarious!
I don't like these decision, made by people that do not use, neither know that well, JavaScript, so they just analyze what's more convenient for their parsers or minifiers, avoiding analysis about what developers truly need.
It does not matter if we have some prototype cloned by some framework, JavaScript is not used only by "framework people", there are thousands of developers able to create their own framework thanks to JS features.
Remove introspection, remove things cause some ignorant wannabe could mess up with things ... it's like they are saying:guys, this language is too good for you while it suppose to be a "toy". You don't study it, you don't understand it, abut you pretend it works as you think.
You are all idiots and newbies ... so we remove cool parts because you don't need introspection, neither complex statements you don't understand ... but be happy, we put some prototype framework methods there ...


Same is for the "this" reference to undefined ... the reason? People could mess up the global scope ... again: we are programmers as you are, not everybody here is a moron
This is how ES5 is coming out, and I personally do not like it.
Cheers

Anonymous said...

They treat us like child, while saying that it's for our own good. In the end, they're falling in the dumb user trap:
Make an idiot-proof tool, and only idiots will use it. Whatever.

I like the examples you wrote, but I have only a minor question:

In the with statement, you define "collection" as "collection || []" and then "length" as "collection ? collection.length : 0". This is not ambiguous, but confusing. What collection does "length" refer? We know the answer, but it's somehow messy, isn't it?

alsanan said...

I've spanished your post.

Josh Powell said...

I always love your posts Andrea, you aren't afraid to take the unpopular side of an issue. These uses of with seems pretty insightful, but I've stayed away from with because everything I've read has said to. maybe I'll play with with now. :)

topfunky said...

Brilliant series of posts and useful examples. This will change the way I write Javascript apps.

We can thank the glacial pace of language development and browser adoption for the continued availability of with for at least a few more years...

Zach said...

About the global scope thing: that's to allow more dependable sandboxing, not to protect you from messing with the global scope, but to protect you from other people messing with the global scope. JavaScript isn't like every other language, because it is the language of the web: scripts are shared, mixed and mashed from third parties all the time, security is playing catch up. I agree with can be useful, though.

Unknown said...

If you care about performance, don't use with. http://jsninja.com/Talk:With_Statements

Andrea Giammarchi said...

If performances matters, with is not the best choice but, again, that test shows nothing.
It is not a real case scenario and "with" used in that way does not make sense.
with (Math){
function benchmark(){
for (var $floor = floor, x = 0, i = 0; i <= 10000; ++i)
x += $floor(i / 4)
;
}
};

Above test is more reasonable, but when performances matter we can use performances strategies:

function benchmark2(){
for (var $floor = Math.floor, x = 0, i = 0; i <= 10000; ++i)
x += $floor(i / 4)
;
}

In few words, it's unfair to compare with in this way while my examples show a reasonable usage and benefits.
Regards

Andrea Giammarchi said...

Actually ... this is faster in Google Chrome:
with(Math){
var benchmark = function(){
for (var $floor = floor, x = 0, i = 0; i <= 10000; ++i)
x += $floor(i / 4)
;
}
};

Andrea Giammarchi said...

and actually, that is faster in IE as well ... still pointless though ...

Unknown said...

It's faster because you are caching the lookup of floor on Math. The function inside the 'with' isolates the var-declared variables, x and i from being looked up in the 'with' scope, so it's not slowed by the 'with'.

The tricky thing about 'with' is that is slows down variables that have nothing to do with the 'with' statement. If you look at my examples it's not obvious that the 'with(Math)' has slowed down the accesses to the i and x variables. But it has, by a factor of around 10.

You may think my example is nothing like real code. I hope you are right. I boiled it down from a benchmark I found on the net.

As for the 'tiny masterpiece' I think it would be clearer with

{
let o = myreference;
o.doStuff();
o.var1 = "whataver";
// etc etc ...
};

which you will be able to do in ES5. For now you have to use 'var'.

wavded said...

Pardon my ignorance... I never have used "with" since I learned that it was bad to use. These examples were helpful to see how it is useful. Could you give an example of why it is "dangerous" for a novice programmer?

Andrea Giammarchi said...

er ... mate, let is available since 2007 - I am not saying with is faster, I am saying everybody keep presenting bad usages and examples, so that's fine since that is my whole point, cheers.

Andrea Giammarchi said...

@Marc, it is dangerous because it brings an invisible scope that could cause troubles only if you assign variables
You'll never find somebody screaming how bad is the "with" statement without showing you some assignment.
If we talk about performances, this is another story, and I have showed here some example for quick inline extended scope that could demonstrate that "with" is maybe not the best performances option, but it is surely elegant and able to create more compact code - more than any other strategy you could use (YUICompressor, Google Closure Compiler, MS Ajax Minifier, etc etc)

Unknown said...

'let' is part of the ECMAScript 5 standard, which was approved last week.

Andrea Giammarchi said...

Erik I am sure ES5 adopted lot of stuff from Mozilla but let, as many other things there, are available since JS 1.7
example
Moreover, it's about 4 years I am trying to normalize Mozilla stuff for other browsers.
Eventually ES5 recognized these methods are a must have but I am partially using ES5 since I have created first JSL.
Regards

wavded said...

Thx @WebReflection.

@Erik - I don't think let and const are apart of ECMAScript 5 but rather being considered for the version after -> see http://developer.yahoo.com/yui/theater/video.php?v=crockford-yuiconf2009-state