Just slides I have showed today here @ nokia for a tech talk, hope you'll enjoy them, cheers
Update
Here you can find the updated version of the Object.forEach proposal: gist 2294934
Main changes are about some inconsistent behavior in Safari, not it should work without problems.
Thanks to @medikoo for his hint.
... rock'n'roll ...
behind the design
My JavaScript book is out!
Don't miss the opportunity to upgrade your beginner or average dev skills.
Showing posts with label ES3. Show all posts
Showing posts with label ES3. Show all posts
Tuesday, April 10, 2012
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.
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:
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.
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.
Funny enough, if we have nested scope that relies in undefined but the outer one has something like:
nothing will happen, undefined is still not trustable.
Fine for eval, but a kinda common arguments trick won't be usable anymore.
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 ...
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.
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).
All cases will throw an error so, once again, hwew ES5 is introducing partial reserved keywords and this is wrong, imho.
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:
Above example is the equivalent of this function:
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.
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:
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.
We cannot do this kind of mistake anymore since a SyntaxError will occur.
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.
To seal/froze the property and to avoid delete operation all we need to do is to mark it as not configurable.
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.
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.
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?
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.
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: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.
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
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.
Wednesday, March 16, 2011
Object.defineHybridProperty
Update Yes, I did it: getters and setters for IE < 9 and other browsers
After my early Hooorrayyyy! about compatible IE < 9 getters and setters, I have been experimenting a bit more on how to solve the JSObject to VBVariant and vice-versa assignment and the result was an horrendous monster loads of potential memory leaks and performances implications for the already slow bounce of browsers such IE8, 7, and 6.
Since limitations were also too many, as described in this even earlier attempt from dojo framework, I have realized that VBScript was simply a no-go, or better, probably the wrong answer to the question: how can I have cross-browser getters and setters?
As showed above, the jQuery().html method can be considered a friendly answer for a cross browser get/set implementation, and surely much more friendly than what antimatter15 proposed some time ago (Pssss! dude, I could not find your real name in the "About" section ...).
However, even using these fallbacks, the descriptor object will result inconsistent because writable, enumerable, and configurable properties won't act as expected.
At this point I have decided to fallback into a "jQuery approach" solution that will behave exactly the same in all old and newer browsers, being still able to use an ES5 like descriptor that in a not that far away future won't even need to be changed at all, it will simply work.
The source code of Object.defineHybridProperty, together with Object.defineHybridProperties, is here and as you can see it's a quite simple and compact piece of code.
Hybrid properties could be confused with methods ... and actually, all hybrid properties that have a get and/or set descriptor, become de-facto an object methods with current constrain: zero arguments to get, 1 single argument to set.
Use cases have been already described, a jQuery like API could use without problems the current approach, making the future ES5 only refactory less painful than whatever other get/set approach.
Indeed, once we know which descriptor is using getters/setters, all we have to do is to remember which property has been made hybrid, and change accordingly each invoke with arguments as assignment, and removing brackets from every other empty invoke.
Please Note I will update later the code in order to properly assign Object.prototype native names such toString so that IE browsers will consider them as well.
Done, it's updated and 409 bytes minified and gzipped, have fun :)
After my early Hooorrayyyy! about compatible IE < 9 getters and setters, I have been experimenting a bit more on how to solve the JSObject to VBVariant and vice-versa assignment and the result was an horrendous monster loads of potential memory leaks and performances implications for the already slow bounce of browsers such IE8, 7, and 6.
Since limitations were also too many, as described in this even earlier attempt from dojo framework, I have realized that VBScript was simply a no-go, or better, probably the wrong answer to the question: how can I have cross-browser getters and setters?
The jQuery Hybrid Answer
Back in 2009, James Padolsey described the jQuery framework as a sort of getters/setters simulator API, comparing the semantic and beauty of non-standard Spidermonkey __defineGetter__ and __defineSetter__, against jQuery coding style, where many "methods" could be considered as getters or setters.
// get the innerHTML of the first node found through the selector
$("#selector").html();
// set the innerHTML of the first(?) node found through the selector
$("#selector").html("");
As showed above, the jQuery().html method can be considered a friendly answer for a cross browser get/set implementation, and surely much more friendly than what antimatter15 proposed some time ago (Pssss! dude, I could not find your real name in the "About" section ...).
My "due jQuery success, why not!" Proposal
In ES5 everything is so natural and simple, Object.defineProperty and Object.defineProperties work like a charm and IE9 with all other browsers as well. We may decide to use good old __defineGetter/Setter__ as fallback for older Opera, Chrome, Safari, or Firefox, but not for IE since these methods are not supported at all.However, even using these fallbacks, the descriptor object will result inconsistent because writable, enumerable, and configurable properties won't act as expected.
At this point I have decided to fallback into a "jQuery approach" solution that will behave exactly the same in all old and newer browsers, being still able to use an ES5 like descriptor that in a not that far away future won't even need to be changed at all, it will simply work.
A generic Person.prototype Descriptor
var personDescriptor = {
// requires council notification on change
name: {
get: function () {
return this._name;
},
set: function (name) {
// notify here the council about this change
this._name = name;
}
},
// this property is unfortunately immutable (via public access)
age: {
get: function () {
return this._age;
}
},
// simply a method that occurs once per year
birthday: {
value: function () {
this._age++;
}
}
};
Current ES5 Person "Class" Example
//* ES5 example
function Person(name, age) {
// a new Person in town
this._age = age || 0; // default, just born
this.name = name;
}
Object.defineProperties(Person.prototype, personDescriptor);
var me = new Person("Andrea", 32);
// will throw an error
// me.age = 20;
alert([me.name, me.age]); // Andrea, 32
me.birthday();
alert(me.age); // 33
//*/
Current ES3 Person "Class" Example
//* ES3 example
Person = function Person(name, age) {
// a new Person in town
this._age = age || 0; // default, just born
// we still want to notify the council,
// no direct this._name set
this.name(name);
}
Object.defineHybridProperties(Person.prototype, personDescriptor);
var me = new Person("Andrea", 32);
// nothing will happen
// me.age(20);
alert([me.name(), me.age()]); // Andrea, 32
me.birthday();
alert(me.age()); // 33
// wanna know what they are?
alert(me.name);
alert(me.age);
alert(me.birthday);
//*/
The source code of Object.defineHybridProperty, together with Object.defineHybridProperties, is here and as you can see it's a quite simple and compact piece of code.
Use Cases and DONTS
The function name should be explicit enough, and it's used to define hybrid properties.Hybrid properties could be confused with methods ... and actually, all hybrid properties that have a get and/or set descriptor, become de-facto an object methods with current constrain: zero arguments to get, 1 single argument to set.
Use cases have been already described, a jQuery like API could use without problems the current approach, making the future ES5 only refactory less painful than whatever other get/set approach.
Indeed, once we know which descriptor is using getters/setters, all we have to do is to remember which property has been made hybrid, and change accordingly each invoke with arguments as assignment, and removing brackets from every other empty invoke.
Done, it's updated and 409 bytes minified and gzipped, have fun :)
Subscribe to:
Comments (Atom)