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

Sunday, June 05, 2011

ES5 and use strict

Update
There was another article about it which has less examples but more complementary points or descriptions.
Moreover, that page links to a specific use strict compatibility page, right now green only with Firefox Aurora and Google Chrome Canary.
However, another page shows more use strict cases as well and Canary seems to miss one check while Webkit Nightly shows all green YES. Opera Next is not there yet while IE10 surprisingly scores all of them except Function declaration in statements.
Well done guys!


on page 233 of the ECMAScript 5th Edition we can read details about "use strict" activation and what it means for our code.
Somebody believes this ES5 feature can help developers to write less errors ... well, I think not everything is that good as it looks.
This post is about all those points with concrete examples.

No OctalIntegerLiteral or OctalEscapeSequence


Base 8 has not many use cases on daily JS tasks. As example, to obtain the number 8 in base 8 we can write something like 010 where:

alert(010 === 8);

The problem is that 01 is not enough to force the engine to consider that number a base 8 and it will be interpreted as 1 indeed. Fair enough, I personally don't give a hack about base 8 so this does not hurt, it's just slightly simplified numbers parsing.
Same story for OctalEscapeSequence, no "octal magic" anymore.

No Global Variables

If our closure has a reference not defined in the outer scope, there is no global variable but a ReferenceError.

(function(){"use strict";
for(i = 0; i < 2; i++) {
// never executed due "i" ReferenceError
}
}());

Above example is a classic one ... or better, a classic for newcomers with PHP or Python background, as example, where local variables are implicit and global one should be explicit.
While Python has a sort of implicit scope lookup with classes, PHP introduced closures only recently (well, 5.3 which should be right now the default minor version in every bloody server).
Since after 1 day of JavaScript development you should have got it (use var for local scope variables) I do believe this is more a limit, rather than a feature.
First of all, the current situation is quite naif since FireFox does not throw anything, it just stop working, while other browsers may nicely ignore this "feature".
Moreover, the moment we want to define a global reference we need to have in our closure a safe reference to the global context or we are simply screwed.
The second part of this point is that if the reference has been defined as writable:false, aka read only as undefined is, a TypeError should be thrown.

(function(){"use strict";
undefined = 123;
// throw TypeError
}());

Funny enough, if we have nested scope that relies in undefined but the outer one has something like:

(function(){"use strict";
var undefined = 123;
// other nested functions
}());

nothing will happen, undefined is still not trustable.

Safer arguments and eval

... but wasn't eval evil? Anyway, in the forth point of use strict specifications we have errors whenever we try to reassign arguments or eval.
Fine for eval, but a kinda common arguments trick won't be usable anymore.

// this will not work anymore
(function (context){"use strict";
arguments = [].slice.call(arguments, 1);
// some operation with arguments as Array
outerCallback.call(context, arguments);
}());


Goodbye arguments caller and callee

Once again, arguments.callee is gone. Moreover, arguments.callee.caller is done as well but in this case it's not about the caller property, the whole callee concept is gone.

(function anonymous(){"use strict";
alert(anonymous.caller); // throws TypeError
arguments.callee; // throws TypeError
}());

They call it "graceful migration", I call it WTF. If arguments is still there and it's a bloody object similar to an Array, why this object should throw an error with an undefined property?
OK, it's about migration, but actually what use strict introduced here is caller and callee as new reserved words, at least for properties of arguments or whatever function ... well done ...

arguments indexes

Whenever you have noticed or not, if you change a named argument value the arguments object will be affected at the same time. Here a basic example:

// before
(function (a, b){
var c = a;
a = b;
b = c;
alert([].slice.call(arguments));
// b, a
}("a", "b"));

// after
(function (a, b){"use strict";
var c = a;
a = b;
b = c;
alert([].slice.call(arguments));
// a, b
}("a", "b"));

To be honest, whenever it helps or not, I wonder who the hell ever used the first dynamic shared arguments indexed property value case on any sort of logic code ... was it an ES3 gotcha? Well, in such case I agree, it does not make fucking sense so ... thanks, I am sure somebody in this world will have problems about this new entry.
However, somebody that does not know JavaScript and programming principles as well may have problems ( nothing personal man, you just did it wrong 'till now ).
Sarcasm a part, it's good to have this clarification on specs.

Bindings and arguments

For strict mode functions, if an arguments object is created the binding of the local identifier arguments to the arguments object is immutable and hence may not be the target of an assignment expression. (10.5).
Well, if you get anything different form what I have said already about redefining the arguments reference/object, please do not hesitate to wake me up in the middle of the night while I am on vacations since seriously I cannot figure out what's this point about.

Unique Object Property Name

With ES3 we could have done something like:

(function (){
var o = {
one: 1,
one: 2
};
alert(o.one); // 2 ???
}());

Now, since properties order is not granted at all even in a classic for(var key in obj){} loop, this point is about being not ambiguous and do the right assignment once. As summary, with use strict above example will produce an error: property name "one" appears more than once in object literal.
Once again, if you ever tried to assign same property more than once ... well, I can just say it's good they made this less ambiguous but I do believe this won't improve anybody code quality (being a mistake every Unit Test would have spot in any case).

arguments and eval as reserved identifiers

It's just like that, an argument cannot be called eval or arguments otherwise we gonna have a SyntaxError.

(function (){"use strict";
function testEval(eval){}
function testArguments(arguments){}
var o = {
get test(eval) {}
set test(eval) {}
};
}());

All cases will throw an error so, once again, hwew ES5 is introducing partial reserved keywords and this is wrong, imho.

Strict eval

I am not sure I got the next point, but here some behavior:

var evil = (function anonymous(){"use strict";
return function (o_O) {
var result = eval(o_O);
alert(b);
return result;
};
}());

evil("function b(){}");
// function b(){'use strict';}


// example 2
var evil = (function anonymous(){"use strict";
var a = 123;
return function (o_O) {
var result = eval(o_O);
alert(b());
return result;
};
}());

evil("function b(){return a}"); // 123
Apparently eval has been maden a bit safer but I can see its evil nature all over the place without problems. Kinda good that function defined through eval inside a strict function are automatically strict as well so I guess this point is about strict inheritance through evaluation.

Strict this

There are different behaviors completely changed and it's not all about undefined === this.
Actually, it's not about this as undefined at all, it's about not changing the reference to something different.
There is a classic trick to obtain the global object in the most secure possible way:

var global = function(){return this}();
alert(global); // [object Window]
// [object global] in node.js

Above example is the equivalent of this function:

function Global() {
return this;
}

window == Global.call() == Global.call(null) == Global.call(undefined);
// true

Untill now, the this reference has always been changed into the global object if the context was null or undefined.
Moreover, the reference has been changed into a proper object reference with primitives values such boolean, number, and string.

// ES3
function previousHello() {
// primitive converted into new Primitive
// e.g. this reference is a new String(s)
alert("Hello " + this); // Hello World

// we can add properties to new String
this.test = 123;
alert(this.test); // 123
}
var s = "World";
previousHello.call("World");
alert(s.test); // undefined
// since properties cannot be attached to a primitive

I don't remember I have ever defined properties runtime when the callback was about primitives values.
To me is like using objects as trash bin since nothing can be possibly reused after the function has been invoked.
This is what is changed in ES5 and use strict, there is no magic anymore when call or apply are used.
A primitive value will be primitive, an undefined one will be undefined and null will be null.
Here the test case:

// ES5
function sayHello() {"use strict";
alert("Hello " + this); // Hello World
this.test = 123;
alert(this.test); // undefined
}
sayHello.call("World");


function nullThis() {"use strict";
alert(this); // null
}
nullThis.call(null);


function undefinedThis() {"use strict";
alert(this); // undefined
}
undefinedThis.call();
// same as
undefinedThis.call(undefined);

It must be said that all these changes make life easier for engines behind the language since call and apply are widely used and all those checks about the context type and its eventual convertion are gone.
At the same time I would have reserved null as only exception to retrieve the global context since we have no more any safe way to do it and this is in my opinion bad.

More greedy delete

While before we could have tried to delete variables, and without success:

(function test(a){
var b = "b";
delete a;
delete b;
delete test;
alert([a, b, test]);
// a,b,function test(){...}
}("a"));

We cannot do this kind of mistake anymore since a SyntaxError will occur.

(function test(a){"use strict";
var b = "b";
delete a;
delete b;
delete test;
alert([a, b, test]);
}("a"));
// SyntaxError
// applying the 'delete' operator to an unqualified name

The fact this was not possible was clear in ES3 specs but ,,, hey, now we know it better.
Only object properties with a configurable option equal to true can be deleted and nothing else.

TypeError on delete

Even if a property is not writable, we can still delete it since it is considered a configuration option.

(function test(a){"use strict";

var o = Object.create(null, {
deletable: {
value: 123,
configurable: true,
writable: false,
enumerable: true
}
});
alert(o.deletable); // 123
// o.deletable = 456; // Error: read-only

// bye bye property
delete o.deletable;
alert(o.deletable); // undefined

// free to manipulate
o.deletable = 456;
alert(o.deletable); // 456

}());

To seal/froze the property and to avoid delete operation all we need to do is to mark it as not configurable.

(function test(a){"use strict";

var o = Object.create(null, {
deletable: {
value: 123,
configurable: false,
writable: false,
enumerable: true
}
});

delete o.deletable;
// Error: property o.deletable is non-configurable
// and can't be deleted

}());

Since to be able to set properties as non configurable we need ES5 already, I think this was a mistake in the use strict rules because I would expect the same behavior for something introduced in ES5 as well as Object.defineProperty is.

Goodbye with statement

Whenever you like it or not, the with statement is gone.
I seriously do not want to spend more than I have done already about it so ... forget it, be happy about the choice and shut up or Mr Crockford and all minifiers will come out the dark wardrobe and punch you in the face.

Reserved arguments and eval identifiers

Everything else about use strict is related to arguments and eval keywords.
These cannot be used in function expressions, declarations, as variables, these cannot be reassigned, these must be used exclusively for what these are ... got it?

Summary

ES5 introduced use strict to let developers be familiar with things that will disappear soon in the next version of JavaScript.
I am not happy about many choices, specially regarding the caller property which was a must have for debugging and introspective purpose but ... hey, engines are not clever enough to activate these things when necessary but these engines are able to swap runtime a totally different behavior between a non strict function and a strict one.
In few words we still do not have full JavaScript potential here because of this transition that is apparently revolutionary behind the scene, surely not the best present ever for all developers that got JavaScript and used few tricks when necessary to improve their application logic and, why not, security.
Well ... deal with "use strict" and put it there by default or shut up for all new version of JavaScript ... this is the way in any case.

8 comments:

sjs said...

> Moreover, the moment we want to define a global reference we need to have in our closure a safe reference to the global context or we are simply screwed.

I think that's the idea. Ads you embed on your site cannot just read things out of the global scope or pollute it.

Nice summary, thanks.

Andrea Giammarchi said...

the problem is that since eval is still there, the moment our library is evaluated (for whatever reason) rather than injected as global script the window, global, or this reference could be different :(

ariehg said...

I don't get why callee is so important - we never really needed it in the first place - we can simplt name our anonymous -

e.addEventListener(
'click'
, function once(){
e.removeEventListener(
'click',once,false
);
}
, false
);

as for caller - I do find the concept of removing it appealing - why should a function know who called it? if we want our function to gain context, we should design it to use apply - which is a much clearer notion of passing context.
I agree with your general sentiment that a language should not impose coding styles on it's users, which is the real problem with strict mode. That is especially the case with 'with' - which is an extremely strong operator when used correctly (for example when writing templating engines).

on the up side - I don't really see a scenario where browser will dump the 'non-strict' versions any time soon - the same way we can still open HTML3 pages

Andrea Giammarchi said...

agreed, callee is not important but caller has no equivalent and when something is disappearing, something that could have been useful for whatever reason, I usually don't like it :)

Anonymous said...

I must say I'm rather opposite to most of your points. I'm not gonna say that I'm enlightened with strict mode but I find it very useful.

I don't miss at all anything that strict mode forbids and basically all stuff that it forbids to me was or bad practice or effect of mistake - now with strict mode this can be quickly taken out.

If I mistakenly declared global variable, or put same name for two properties in object literal I'd like to get that quickly, strict mode gives me that, and if tests for my code have full coverage, then I can be sure I spot all such errors before production.

Another example:
delete b;

I think it's very sane to throw error on that.
Why would you write such code? The thing is that you would write that only by mistake, in ES3 it's silent, in strict mode it's error. I think it's helpful.
Imagine you typed comma instead of dot:

delete a,b;

In ES3 it may take you a while to track this error, in ES5 it's instant and obvious.

The only issue I find with strict mode is strictness on production environment. What if mistakenly I declared my variable as global, and my unit tests didn't cover it ? On production with strict mode it will crash, while in ES3 it will silently pass, which for production should be way to go.

Andrea Giammarchi said...

hey Medyk, I use "use strict" since the very beginning.
All I am saying is that it did not change a thing on my daily code, neither improved.

I rather wonder why developers were making those mistakes before but Errors are surely welcome.

What is not welcome, as example, is that I have often used "arguments" as named function argument because arguments reference and magic is going to disappear at some point.

I have always thought engines could have been clever enough to get rid of arguments but they rather made it protected reference.

This and other minor gotchas I am not that happy about do not mean we should not use "use strict" ASAP ... actually, I would like to have it by default and eventually deactivate it only if absolutely necessary ( I don't like tons of libraries with redundant "use strict" everywhere )

Btw, this post was a point2point summary of that specs page, it's not about good or bad, it's about explaining them and comment them with my personal opinion.

Anonymous said...

Andrea, thanks for clarification. I thought that you wanted to say "Strict mode is bad" and started pointing its flaws, that's how I read it.

So more less we agree. I also don't like writing 'use strict' everywhere, but when I think about this design decision, I assume that probably there was no better way to introduce it.

Thanks for this post :)

Anonymous said...

AFAIK, Number only stores the value, not the base. Therefore, the point whether 01 is interpreted as base8 or not doesn't matter.