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

Sunday, October 11, 2009

ES5 and function default scope

This is just a quick one. One of the worst decision about ES5, after the one to remove arguments.callee when "use strict" is present, is the missed default reference to the global object. Probably the most secure trick to retrieve the native window in JavaScript is this one:

// wherever we are
var global = function(){return this}();

// while window, could be simply a variable
// assigned somewhere else in this scope


I have already talked about this, and I am still against some ES5 "feature" which aim is probably the one to make JavaScript as fast as possible, regardless all code that will break if "use strict" is present in the generic scope.

Another non-sense is to allow a this in a context where there is no this. Rather than an error, ES5 will point refer this to undefined.
Are you kidding me? this is a special keywords related to the context and you are telling me a context is undefined? so I guess open(), as example, should throw an error there, since window cannot be implicit in an undefined context, isn't it? ... anyway, fortunately the whole problem could be quickly solved in this way:

// ES5 problem
(function(){
"use strict";

this === undefined; // true ... LOL!!!

})();


// WebReflection QAD solution
(function(){
"use strict";

this === window; // true ... yeah baby!

}).call(this);

This simple modification could avoid lots of headaches plus could avoid scope resolution for every window variable.

It's probably quite obvious but I can already imagine lots of people changing the whole function body rather than add a call at the end :D

The trick to retrieve the global context won't work in any case for nested functions, unless we are not sure that this points to window, but being the trick goal the one to make window resolution quick and safe, there is not window,self,top, whatever check that could solve the problem (unless [].sort() will be still buggy ...)

As summary, the day JavaScript will be that fast thanks to removed dynamic and run-time features, it won't be JavaScript anymore but another language a la ActionScript 2 when ActionScript 1 was enough, and the problem was the player rather than the language, which indeed has been replaced by ActionScript 3 even if the problem is still the player and not the language, since it can be compilable into old versions.

5 comments:

Kris Zyp said...

This behavior was not motivated by performance, but rather security. One of the major goals behind ES5 was to be able to use the object capability model to securely execute code. Being able to access the global via (function() return this;)() is a heinous violation of the object capability model, as it gives code ambient authority without being granted that authority by the caller. Of course you are free to argue that the object capability model is not a worthy goal of ES language design...

Andrea Giammarchi said...

it's funny Kris how you consider a security problem something which aim is to guarantee we are dealing with original global object rather than the a modified/injected/fake one as window, top, self, or parent, could be.

At least now I know it was not for performances, but for something else which, to be honest, makes even less sense to me.

Thanks in any case for the explanation.

Regards

kangax said...

Another pattern we can use is:

(function(window /* or `global` if you like */ ){
// ... `window` or `global`
})(this);

Too bad our solutions are not very portable (as you said) — moving function into a different scope can mess things up.

This is one strict-mode addition I never liked. Good to see we agree.

Andrea Giammarchi said...

eventually @kangax :D

Your snippet is OK as well but I was suggesting call mainly to avoid changing anything in the scope, except the end of the function.

I have personally used this to refer the windows in different pieces of code, and sometimes I re-define internally the window var. It could sounds silly, but that's why I proposed call :D

Unknown said...

It looks like the current version of jslint says that both solutions are invalidate "strict". However ff4b8 seems to work ok with it :)