Previously I analyzed ECMAScript 5’s Object and Property system. This is a huge new aspect of the language and deserved its special consideration.
There are a number of other new features and APIs that need attention, as well. The largest of which are Strict Mode and native JSON support.
Strict Mode
Strict Mode is a new feature in ECMAScript 5 that allows you to place a program, or a function, in a “strict” operating context. This strict context prevents certain actions from being taken and throws more exceptions (generally providing the user with more information and a tapered-down coding experience).
Since ECMAScript 5 is backwards-compatible with ECMAScript 3, all of the “features” that were in ECMAScript 3 that were “deprecated” are just disabled (or throw errors) in strict mode, instead.
Strict mode helps out in a couple ways:
- It catches some common coding bloopers, throwing exceptions.
- It prevents, or throws errors, when relatively “unsafe” actions are taken (such as gaining access to the global object).
- It disables features that are confusing or poorly thought out.
Most of the information about strict mode can be found in the ES5 specification [PDF] on page #235.
It should be noted that ECMAScript 5’s strict mode is different from the strict mode available in Firefox (which can be turned on by going to about:config and enabled javascript.options.strict). ES5’s strict mode complains about a completely different set of potential errors (whereas Firefox’s existing strict mode tries to enforce some good practices, only).
How do you enable strict mode?
Simple. Toss this at the top of a program to enable it for the whole script:
"use strict";Or place it within a function to turn on strict mode only within that context.
function imStrict(){ "use strict"; // ... your code ... }Note the syntax that’s used to enable strict mode (I love this!). It’s simply a string in a single statement that happens to contain the contents “use strict”. No new syntax is introduced in order to enable strict mode. This is huge. This means that you can turn strict mode on in your scripts – today – and it’ll have, at worst, no side effect in old browsers.
As you may note from the examples here and in the previous post there are virtually no new syntax additions or changes to the language in ECMAScript 5. This means that you can write your ES5 scripts in a manner that will be able to gracefully degrade for older useragents – something that wasn’t possible with ECMAScript 4. The way in which strict mode is enabled is a great illustration of that point in practice.
A neat aspect of being able to define strict mode within a function is that you can now define complete JavaScript libraries in a strict manner without affecting outside code.
// Non-strict code... (function(){ "use strict"; // Define your library strictly... })(); // Non-strict code...A number of libraries already use the above technique (wrapping the whole library with an anonymous self-executing function) and they will be able to take advantage of strict mode very easily.
So what changes when you put a script into strict mode? A number of things.
Variables and Properties
An attempt to assign foo = "bar"; where ‘foo’ hasn’t been defined will fail. Previously it would assign the value to the foo property of the global object (e.g. window.foo), now it just throws an exception. This is definitely going to catch some annoying bugs.
Any attempts to write to a property whose writable attribute is set to false, delete a property whose configurable attribute is set to false, or add a property to an object whose extensible attribute is set to false will result in an error (these attributes were discussed previously). Traditionally no error will be thrown when any of these actions are attempted, it will just fail silently.
Deleting a variable, a function, or an argument will result in an error.
var foo = "test"; function test(){} delete foo; // Error delete test; // Error function test2(arg) { delete arg; // Error }Defining a property more than once in an object literal will cause an exception to be thrown
// Error { foo: true, foo: false }eval
Virtually any attempt to use the name ‘eval’ is prohibited – as is the ability to assign the eval function to a variable or a property of an object.
// All generate errors... obj.eval = ... obj.foo = eval; var eval = ...; for ( var eval in ... ) {} function eval(){} function test(eval){} function(eval){} new Function("eval")Additionally, attempts to introduce new variables through an eval will be blocked.
eval("var a = false;"); print( typeof a ); // undefinedFunctions
Attempting to overwrite the arguments object will result in an error:
arguments = [...]; // not allowed
Defining identically-named arguments will result in an error function( foo, foo ) {}.
Access to arguments.caller and arguments.callee now throw an exception. Thus any anonymous functions that you want to reference will need to be named, like so:
setTimeout(function later(){ // do stuff... setTimeout( later, 1000 ); }, 1000 );The arguments and caller properties of other functions no longer exist – and the ability to define them is prohibited.
function test(){ function inner(){ // Don't exist, either test.arguments = ...; // Error inner.caller = ...; // Error } }Finally, a long-standing (and very annoying) bug has been resolved: Cases where null or undefined is coerced into becoming the global object. Strict mode now prevents this from happening and throws an exception instead.
(function(){ ... }).call( null ); // Exceptionwith(){}
with(){} statements are dead when strict mode is enabled – in fact it even appears as a syntax error. While the feature was certainly mis-understood and possibly mis-used I’m not convinced that it’s enough to be stricken from the record.
The changes made in ECMAScript 5 strict mode are certainly varied (ranging from imposing stylistic preferences, like removing with statements, to fixing legitimately bad language bugs, like the ability to redefine properties in object literals). It’ll be interesting to see how people begin to adopt these points and how it’ll change JavaScript development.
All that being said, I’m fairly certain that jQuery is ES5-Strict compatible right now. Once an implementation of the language is made available (so that that premise may be tested) I’ll happily switch jQuery over to working exclusively in strict mode.
JSON
The second major feature of the language is the addition of native JSON support to the language.
I’ve been championing this move for a long time and I’m glad to see it finally arrive in a specification.
In the meantime PLEASE start migrating your JSON-using applications over to Crockford’s json2.js. It is fully compatible with the ECMAScript 5 specification and gracefully degrades if a native (faster!) implementation exists.
In fact, I just landed a change in jQuery yesterday that utilizes the JSON.parse method if it exists, now that it has been completely specified.
There are two primary methods for handling JSON: JSON.parse (which converts a JSON string into a JavaScript object) and JSON.stringify (which convert a JavaScript object into a serialized string).
JSON.parse( text )
Converts a serialized JSON string into a JavaScript object.
var obj = JSON.parse('{"name":"John"}'); // Prints 'John' print( obj.name );JSON.parse( text, translate )
Use a translation function to convert values or remove them entirely.
function translate(key, value) { if ( key === "name" ) { return value + " Resig"; } } var obj = JSON.parse('{"name":"John","last":"Resig"}', translate); // Prints 'John Resig' print( obj.name ); // Undefined print( obj.last );JSON.stringify( obj )
Convert an object into a serialized JSON string.
var str = JSON.stringify({ name: "John" }); // Prints {"name":"John"} print( str );JSON.stringify( obj, ["white", "list"])
Serialize only a specific white list of properties.
var list = ["name"]; var str = JSON.stringify({name: "John", last: "Resig"}, list); // Prints {"name":"John"} print( str );JSON.stringify( obj, translate )
Serializes the object using a translation function.
function translate(key, value) { if ( key === "name" ) { return value + " Resig"; } } var str = JSON.stringify({"name":"John","last":"Resig"}, translate); // Prints {"name":"John Resig"} print( str );JSON.stringify( obj, null, 2 )
Adds the specified number of spaces to the output, printing it evenly.
var str = JSON.stringify({ name: "John" }, null, 2); // Prints: // { // "name": "John" // } print( str );JSON.stringify( obj, null, "\t" )
Uses the specified string to do the spacing.
var str = JSON.stringify({ name: "John" }, null, "\t"); // Prints: // {\n\t"name": "John"\n} print( str );Additionally, a few new generic methods have been added to some of the base objects but, frankly, they aren’t that interesting. The results from String, Boolean, and Number are just equivalent to calling .valueOf() and the result from Date is equivalent to calling .toISOString()
// Yawn... String.prototype.toJSON Boolean.prototype.toJSON Number.prototype.toJSON Date.prototype.toJSON.bind()
A welcomed addition to the language is a built-in .bind() method for enforcing the context of a function (virtually identical to Prototype’s .bind implementation).
Function.prototype.bind(thisArg, arg1, arg2....)
Enforces the ‘this’ of the specified function to a specific object – and passing in any specified arguments.
var obj = { method: function(name){ this.name = name; } }; setTimeout( obj.method.bind(obj, "John"), 100 );Considering how long this function (and its equivalents) have been around it’s a welcome addition to the language.
Date
Dates are now capable of both parsing and outputting ISO-formatted dates. Thank goodness, about time. rimshot
The Date constructor now attempts to parse the date as if it was ISO-formatted, first, then moves on to the other inputs that it accepts.
Additionally, date objects now have a new .toISOString() method that outputs the date in an ISO format.
var date = new Date("2009-05-21T16:06:05.000Z"); // Prints 2009-05-21T16:06:05.000Z print( date.toISOString() );.trim()
A native, built-in, .trim() is now included for strings. Works identically to all the other trim methods out there – with the potential to possibly work faster.
Steven Levithan has discussed the trim method in great depth.
Array
The JavaScript Array Extras that’ve been around for, what seems like, forever are finally formally specified. This includes the following methods: indexOf, lastIndexOf, every, some, forEach, map, filter, reduce, and reduceRight.
Additionally a new Array.isArray method is included, providing functionality very similar to the following:
Array.isArray = function( array ) { return Object.prototype.toString.call( array ) === "[object Array]"; };Altogether I think ECMAScript 5 makes for an interesting package. It isn’t the massive leap that ECMAScript 4 promised but it is a series of respectable improvements that reduces the number of obvious bugs while making the language safer and faster. I’m looking forward to when some implementations start to go public.
Greg (May 22, 2009 at 7:12 am)
I’m not clear on why it’s a good thing that arguments.caller and arguments.callee are deprecated/disabled. Could you explain? I thought they were useful and neither ambiguous nor error-prone.
John Resig (May 22, 2009 at 7:26 am)
@Greg: I did some digging and found a thread where they discuss this issue. It seems like the biggest problem is passing the arguments object to another function, where it is capable of accessing the callee/caller information, without your knowledge. Personally, I’m going to miss arguments.callee, I used it in a number of places in my code.
Harley Jones (May 22, 2009 at 7:54 am)
@Greg, @John: I’ve been using arguments.caller and arguments.callee to create a stack trace for error handling. Can this be accomplished another way? I suppose I could use a block of code outside the “use script” scope. Is there a “stop strict”?
John Resig (May 22, 2009 at 8:00 am)
@Harley: No, it doesn’t appear as if there’s any way to break out of strict mode, once you’re in it. Your specific use case seems fairly safe since it’s a library that would be included during the debug process, so you could ask them to disabled strict mode explicitly.
Kevin van Zonneveld (May 22, 2009 at 8:09 am)
Thanks for the heads up John, this looks awesome!
Joe Larson (May 22, 2009 at 8:16 am)
I keep hearing various versions of this: “No new syntax is introduced in order to enable strict mode. This is huge. This means that you can turn strict mode on in your scripts – today – and it’ll have, at worst, no side effect in old browsers.”.
Except that, as soon as I start writing code that expects to be running in a strict context, that code may not fail in a ES3 browser, but it sure won’t do what I want. Some of the degradation will be graceful– inability to lock down properties, etc, we have to live with that today. Other degradation is not so graceful– for example I can’t write code that relies on the enumerable setting for a property being on or off. Which means I will either have to say “only ES5 browsers” or have two sets scripts or have one set of scripts with lots of switches. This seems painful. What am I missing?
John Resig (May 22, 2009 at 8:26 am)
@Joe Larson: I think you misinterpreted what I said – I didn’t say that all ECMAScript 5 code is backwards compatible to ECMAScript 3 – just that the syntax is backwards compatible. You’ll be able to use feature detection to determine if the newly-added features work as you expect them to and be able to provide a graceful fallback if they don’t.
Now, if there was absolutely no new logic introduced in ES5 then that would be a sad language update indeed – going nowhere very slowly.
Having two versions of a script seems fine to me, if you’re taking explicit advantage of some of the advanced features (like Object.defineProperty) AND you MUST continue to support all browsers – since any sort of “graceful” degradation there simply isn’t possible – you’d be under an inferior runtime.
Strict mode is one thing that we can start to use immediately in all browsers (especially since it’s mostly useful as a debugging tool). We’ll have to wait a bit before we can use the others everywhere, as well. That being said – for those working in a single environment (developing Firefox extensions, writing ActionScript, iPhone applications, or other single-runtime applications) will receive an immediate, and large, benefit from this language update.
Joe Larson (May 22, 2009 at 8:38 am)
John — ok, thanks for the clarification. It just seems like the language being used keeps alluding to some kind of magical backwards compatibility, and as you rightly say, that is sort of nonsensical for a real language upgrade. As long as the pain is acknowledged, that’s fine, two scripts is worth it. And hopefully we don’t have to live with that for long, or at all in some circumstances.
Andrea Giammarchi (May 22, 2009 at 9:16 am)
arguments.callee … for god sake do not touch it!
I cannot imagine a global scope where every single function will persist causing naming conflicts everywhere because of this pointless choice!
setTimeout(function a(){}, 1);
alert(this.a);
In every Internet Explorer above code will create the CHAOS … it seems that everything is Douglas Crockford choice ( arguments.callee as an error ) and YUI Compressor ( with( … ) ) these two things will make backward compatibility in strict mode a nightmare.
Finally, again vice-versa project, Array is now 1.8 almost full specs.
Mariusz Nowak (May 22, 2009 at 9:20 am)
What is your recommended way of implementation this ECMAScript 5 features in ECMAScript 4 engines? Should libraries edit native objects and set those methods on them e.g.:
if (!Array.isArray) {
Array.isArray = function (obj) { ... }
}
Or maybe we should keep away from that. Some other js code on webpage may do some sniffing based on existence of those methods. If we put them in with our library then “other code” may not work as expected – this is of course rare case and doesn’t say much good about “other code” but still – should we care about that ?
How do you deal with that in jQuery ?
Nick (May 22, 2009 at 9:24 am)
I may well be missing something but what is the difference between .bind() and .call()?
TNO (May 22, 2009 at 9:24 am)
@Greg and John Resig:
The arguments object in general is being set up to be removed from the language and replaced with named, optional and rest parameters instead.
arguments.callee can be replaced with a named function literal:
var foo = function self(){
...
self()
}
“self” will exist only in the scope of that function, not globally, so this will be shorter code that arguments.callee.
As for arguments.caller, I don’t believe that was ever part of the spec anyway… Plus it seems that the next version (after ECMAScript 5) should provide separate stack tracing functionality (possibly on the Error object)
Andrea Giammarchi (May 22, 2009 at 9:35 am)
@Mariusz Nowak, if Array.isArray and others methods are full specs there is not a single problem to use it with or without sniffing. I mean, an indexOf is an indexOf, it is extremely useful and if there is a sniff, will be 99% of the time to implement it if not present.
@Nick, bind prevent injected scope or global one, you can pre specify a scope and call the function as is, without even attaching it to an object.
var a = {doStuff:function(){alert(this.name)}, name:"a"};
var b = {name:"b"};
var c = a.doStuff.bind(b);
c(); // "b"
@TNO, that will break in every Internet Explorer because as soon as you create a function with a name this will persist with its name in the scope. arguments.callee has nothing bad at all, I cannot spot a single reason to remove it or make it deprecated in strict mode.
Harley Jones (May 22, 2009 at 9:37 am)
I think the argument was well made on the mailing list that arguments.callee.caller is NECESSARY for building a stack-trace. And I do NOT think this is a use-case that is segregated to “debug-mode” only. When wacky stuff happens in the field (in front of the customer), I need all the information I can get. The customer is rarely capable of describing the conditions and situation that brought on the exception. A thorough stack-trace is probably the best tool for debugging this type of thing.
Of course, if all browser vendors supported window.onerror and Error.prototype.stack, we’d have a fallback. But Firefox offers “stack”, Opera offers “stacktrace”, and they’re inconsistent. Only Firefox and IE support window.onerror, and again, they’re inconsistent. Some browsers expose <SCRIPT> loading errors, and some browsers expose <IMG> loading errors.
I understand the security concerns involved in arguments.callee; and I think that should be examined closely. But, it’s my opinion that JS/browser exception handling sucks pretty bad. Removing arguments.callee.caller is a big step in the WRONG direction.
Andrea Giammarchi (May 22, 2009 at 9:59 am)
@Harley Jones, I do not get security problems at all about callee …
Harley Jones (May 22, 2009 at 10:12 am)
In short, it’s the Principle of Least Authority (POLA). Who’s got access to your code? And we’re not just talking about the ability to view your code. If your code calls mine, I can use arguments.callee.caller to view/modify your code. Likewise, I can use arguments.callee.caller.arguments to view/modify your arguments.
That’s a pretty severe security risk. My applications run within a secured intranet and I have control over the scripts being executed. So, it’s not a large concern for me. However, what’s to stop a foolish user from downloading malware that installs a GreaseMonkey-type app or an ActiveX that can do untold damage?
Of course, that type of argument falls apart (in my opinion). Afterall, what’s to stop a foolish user from doing anything that’s, well, foolish?
Like I said, I understand the “concern.” But, that concern might be a molehill that’s been turned into a mountain.
TNO (May 22, 2009 at 10:27 am)
@Andrea Giammarchi:
Surely your argument isn’t to avoid implementing new features because IE doesn’t support it right now? The JScript team has stated they plan to follow the spec on this. In the link John Resig has posted in his comment above, you can read the same discussion. If you dislike it and have a better idea, no one is stopping you from posting your concerns on the mailing list. Shooting the messenger won’t get you anywhere….
Andrea Giammarchi (May 22, 2009 at 10:37 am)
@Harley Jones, still, I do not get concrete applications of these problems … at all. I do not eval stuff before I use arguments, usually I eval at the end of a funciton execution or async (readyState === 4, what can you do that bad? and what if I eval outside the function scope as we almost all do?)
If somebody can inject code inside my function, he can change the function itself (think inside a setTimeout) so security problems are not solved at all unless everything become sealed/frozen. In any case, I am talking about callee, not caller, just arguments.callee, if you have that you will not break every Internet Explorer version with potentially hundreds of function name conflicts in the global scope. This is my point. About with, well, if you do not know what you are doing, it cannot be a language spec problem. with is a problem only for YUI Compressor and hilariously enough, with with you can save a lot of characters:
with(document.body){
appendChild(node);
attachEvent("something", function bleah(){});
removeChild(otherNode);
with(firstChild){
parentNode.removeChild(anotherNode);
};
};
where is the problem here exactly? don’t get this as well, but I prefer “no with” rather than “no arguments.callee”
@TNO, I am not shooting, I am just trying to understand your points to eventually open a better post in ML. All this ES5 idea was to avoid drastic changes over what we have so far but these two things will create a lot of compatibility problems. That’s it.
@TNO, John, sorry I did not see your comment with the link. Going there to talk about it.
Jeffrey Gilbert (May 22, 2009 at 10:58 am)
Out of curiosity, have you heard if there will be anything like the suggested typecasting support that got dropped from the proposed ECMA4 in future iterations or is that just plain off the dev board due to legacy needs?
Off topic, but jetpack looks awesome with JQuery built in. I can’t wait to mess with that since it’s like grease monkey + a supercharger. Is that the results of fuel or is that another story all together? I haven’t gotten into xul dev at all.
Chris Wanstrath (May 22, 2009 at 11:06 am)
Ahhh this is freaking sweet, I can’t wait to start using some of this stuff.
Thanks for the posts summarizing!
John Resig (May 22, 2009 at 11:23 am)
@Nick: .call() immediately executes the function with a specific ‘this’ set – whereas .bind() *returns* a function that, when called, will have a specific ‘this’ set. Using .bind() you can pass a function some place else, where it will be executed at a later time, and ensure that its ‘this’ will remain intact.
@Jeffrey Gilbert: I haven’t heard anything yet about the features of the next version of ECMAScript, but considering how cautious the ECMAScript committee is being now, with new features, I think it’s very doubtful that we’ll see the type system from ECMAScript 4.
asf (May 22, 2009 at 11:57 am)
should the strict thing not be versioned from the start? “use strict(1.0)”; or whatever
schmichael (May 22, 2009 at 12:07 pm)
I’m with asf, what happens when ECMAScript 6 comes out? Will the meaning of “use strict”; change or will it be augmented with a version?
As a Python coder I couldn’t help but think of a couple lines from the Zen of Python:
Explicit is better than implicit.
In the face of ambiguity, refuse the temptation to guess.
Seems like “use strict”; implicitly means “use strict mode according to ECMAScript 5”.
David Smith (May 22, 2009 at 12:33 pm)
Andrea Giammarchi: Many of these restrictions make it possible to have faster javascript engines. The mere existence of ‘eval’ and the arguments object disables or greatly increases the complexity of certain optimizations (for example, consider that Number.prototype.toString = eval; is valid js. It’s very difficult to tell when a function with one String argument will suddenly change everything, and as such it’s very difficult to make optimizations that require reordering code past function calls).
As for ‘with’, http://twitter.com/ohunt/status/1812802546 sums it up better than I can, and from someone with far more expertise than me.
TNO (May 22, 2009 at 12:42 pm)
@Jeffrey Gilber, @John Resig:
Mark Miller mentioned it to me a couple days ago on Ajaxian that a type system is still on the table for post ES5. There are a few issues left to hash out but we can at least see what the current direction is:
http://wiki.ecmascript.org/doku.php?id=strawman:types
Dave (May 22, 2009 at 12:51 pm)
@Andrea: Requiring a name for functions rather than using arguments.callee will cause global namespace pollution, I agree. In JScript I seem to recall that even nested functions pollute the global name space so it would be tough to write backwards-compatible code:
http://nedbatchelder.com/blog/200812/internet_explorer_mystery_1376.html
@Harley: It sure would be nice if they could tackle the error reporting stuff and get it right for version 5, because I use it heavily as well.
Elijah Grey (May 22, 2009 at 2:38 pm)
Does "use strict"; stay confined within curly braces {} (labeled or otherwise) code blocks too? For example:
some_code_block: {
"use strict";
let a = 0;
with ({a:1}) {
print(a);
}
}
John Resig (May 22, 2009 at 2:46 pm)
@asf, schmichael: I assume that once the semantics of “use strict” is changed there’s going to be changes to the underlying syntax of the language, as well – and when that happens developers will have to opt in to the new version of the language anyway (e.g. <script type=”application/ecmascript+6.0″>, or some such).
@Elijah: No, the strict mode can only be scoped to functions and entire applications, not blocks.
Michael Schurter (schmichael) (May 22, 2009 at 2:58 pm)
@John Resig: I think what asf and I are wondering is wouldn’t it be better to have a standard way of specifying the preferred strictness level to make backwards compatibility in the future easier. So “use strict 5”; in an ECMAScript 6 parser would *not* enable strict mode.
Perhaps you could conditionally enable “use strict”?
if (ecmascript.version