Sunday, June 5, 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.

No comments:

Post a Comment