ECMAScript 5 is on its way. Rising from the ashes of ECMAScript 4, which got scaled way back and became ECMAScript 3.1, which was then re-named ECMAScript 5 (more details)- comes a new layer of functionality built on top of our lovable ECMAScript 3.
Update: I’ve posted more details on ECMAScript 5 Strict Mode, JSON, and More.
There are a few new APIs included in the specification but the most interesting functionality comes into play in the Object/Property code. This new code gives you the ability to dramatically affect how users will be able to interact with your objects, allowing you to provide getters and setters, prevent enumeration, manipulation, or deletion, and even prevent the addition of new properties. In short: You will be able to replicate and expand upon the existing JavaScript-based APIs (such as the DOM) using nothing but JavaScript itself.
Perhaps best of all, though: These features are due to arrive in all major browsers. All the major browser vendors worked on this specification and have agreed to implement it in their respective engines. The exact timeframe isn’t clear yet, but it’s going to be sooner, rather than later.
There doesn’t appear to exist a full implementation of ES5 at this point, but there are a few in the works. In the meantime you can read the ECMAScript 5 specification (PDF – I discuss pages 107-109 in this post) or watch the recent talk by some of the ECMAScript guys at Google.
Note: I’ve provided a couple simple, example, implementations for these methods to illustrate how they might operate. Almost all of them require other, new, methods to work correctly – and they are not implemented to match the specification 100% (for example there is no error checking).
Objects
A new feature of ECMAScript 5 is that the extensibility of objects can now be toggled. Turning off extensibility can prevent new properties from getting added to an object.
ES5 provides two methods for manipulating and verifying the extensibility of objects.
Object.preventExtensions( obj )
Object.isExtensible( obj )
preventExtensions
locks down an object and prevents and future property additions from occurring. isExtensible
is a way to determine the current extensibility of an object.
Example Usage:
var obj = {}; obj.name = "John"; print( obj.name ); // John print( Object.isExtensible( obj ) ); // true Object.preventExtensions( obj ); obj.url = "https://johnresig.com/"; // Exception in strict mode print( Object.isExtensible( obj ) ); // false
Properties and Descriptors
Properties have been completely overhauled. No longer are they the simple value associated with an object – you now have complete control over how they can behave. With this power, though, comes increased complexity.
Object properties are broken down into two portions.
For the actual “meat” of a property there are two possibilities: A Value (a “Data” property – this is the traditional value that we know and love from ECMAScript 3) or a Getter and Setter (an “Accessor” property – we know this from some modern browsers, like Gecko and WebKit).
- Value. Contains the value of the property.
- Get. The function that will be called when the value of the property is accessed.
- Set. The function that will be called when the value of the property is changed.
Additionally, properties can be…
- Writable. If false, the value of the property can not be changed.
- Configurable. If false, any attempts to delete the property or change its attributes (Writable, Configurable, or Enumerable) will fail.
- Enumerable. If true, the property will be iterated over when a user does
for (var prop in obj){}
(or similar).
Altogether these various attributes make up a property descriptor. For example, a simple descriptor might look something like the following:
{ value: "test", writable: true, enumerable: true, configurable: true }
The three attributes (writable, enumerable, and configurable) are all optional and all default to true. Thus, the only property that you’ll need to provide will be, either, value or get and set.
You can use the new Object.getOwnPropertyDescriptor
method to get at this information for an existing property on an object.
Object.getOwnPropertyDescriptor( obj, prop )
This method allows you to access the descriptor of a property. This method is the only way to get at this information (it is, otherwise, not available to the user – these don’t exist as visible properties on the property, they’re stored internally in the ECMAScript engine).
Example Usage:
var obj = { foo: "test" }; print(JSON.stringify( Object.getOwnPropertyDescriptor( obj, "foo" ) )); // {"value": "test", "writable": true, // "enumerable": true, "configurable": true}
Object.defineProperty( obj, prop, desc )
This method allows you to define a new property on an object (or change the descriptor of an existing property). This method accepts a property descriptor and uses it to initialize (or update) a property.
Example Usage:
var obj = {}; Object.defineProperty( obj, "value", { value: true, writable: false, enumerable: true, configurable: true }); (function(){ var name = "John"; Object.defineProperty( obj, "name", { get: function(){ return name; }, set: function(value){ name = value; } }); })(); print( obj.value ) // true print( obj.name ); // John obj.name = "Ted"; print( obj.name ); // Ted for ( var prop in obj ) { print( prop ); } // value // name obj.value = false; // Exception if in strict mode Object.defineProperty( obj, "value", { writable: true, configurable: false }); obj.value = false; print( obj.value ); // false delete obj.value; // Exception
Object.defineProperty
is a core method of the new version of ECMAScript. Virtually all the other major features rely upon this method existing.
Object.defineProperties( obj, props )
A means of defining a number of properties simultaneously (instead of individually).
Example Implementation:
Object.defineProperties = function( obj, props ) { for ( var prop in props ) { Object.defineProperty( obj, prop, props[prop] ); } };
Example Usage:
var obj = {}; Object.defineProperties(obj, { "value": { value: true, writable: false }, "name": { value: "John", writable: false } });
Property descriptors (and their associated methods) is probably the most important new feature of ECMAScript 5. It gives developers the ability to have fine-grained control of their objects, prevent undesired tinkering, and maintaining a unified web-compatible API.
New Features
Building on top of these new additions some interesting new features have been introduced into the language.
The following two methods are very useful for collecting arrays of all the properties on an object.
Object.keys( obj )
Returns an array of strings representing all the enumerable property names of the object. This is identical to the method included in Prototype.js.
Example Implementation:
Object.keys = function( obj ) { var array = new Array(); for ( var prop in obj ) { if ( obj.hasOwnProperty( prop ) ) { array.push( prop ); } } return array; };
Example Usage:
var obj = { name: "John", url: "https://johnresig.com/" }; print( Object.keys(obj).join(", ") ); // name, url
Object.getOwnPropertyNames( obj )
Nearly identical to Object.keys
but returns all property names of the object (not just the enumerable ones).
An implementation isn’t possible with regular ECMAScript since non-enumerable properties can’t be enumerated. The output and usage is otherwise identical to Object.keys
.
Object.create( proto, props )
Creates a new object whose prototype is equal to the value of proto and whose properties are set via Object.defineProperties( props )
.
A simple implementation would look like this (requires the new Object.defineProperties
method).
Example Implementation: (by Ben Newman)
Object.create = function( proto, props ) { var ctor = function( ps ) { if ( ps ) Object.defineProperties( this, ps ); }; ctor.prototype = proto; return new ctor( props ); };
Other implementation:
Object.create = function( proto, props ) { var obj = new Object(); obj.__proto__ = proto; if ( typeof props !== "undefined" ) { Object.defineProperties( obj, props ); } return obj; };
Note: The above code makes use of the Mozilla-specific
__proto__
property. This property gives you access to the internal prototype of an object – and allows you to set its value, as well. The ES5 methodObject.getPrototypeOf
allows you to access this value but not set its value – thus the above method cannot be implement in a generic, spec-compatible, manner.I discussed
Object.getPrototypeOf
previously so I won’t bother discussing it again here.
Example Usage:
function User(){} User.prototype.name = "Anonymous"; User.prototype.url = "http://google.com/"; var john = Object.create(new User(), { name: { value: "John", writable: false }, url: { value: "http://google.com/" } }); print( john.name ); // John john.name = "Ted"; // Exception if in strict mode
Object.seal( obj )
Object.isSealed( obj )
Sealing an object prevents other code from deleting, or changing the descriptors of, any of the object’s properties – and from adding new properties.
Example Implementation:
Object.seal = function( obj ) {
var props = Object.getOwnPropertyNames( obj );
for ( var i = 0; i < props.length; i++ ) {
var desc = Object.getOwnPropertyDescriptor( obj, props[i] );
desc.configurable = false;
Object.defineProperty( obj, props[i], desc );
}
return Object.preventExtensions( obj );
};[/js]
You would seal an object if you want its existing properties to stay intact, without allowing for new additions, but while still allowing the user to write to or edit the properties.
Object.freeze( obj )
Object.isFrozen( obj )
Freezing an object is nearly identical to sealing it but with the addition of making the properties un-editable.
Example Implementation:
Object.freeze = function( obj ) {
var props = Object.getOwnPropertyNames( obj );
for ( var i = 0; i < props.length; i++ ) { var desc = Object.getOwnPropertyDescriptor( obj, props[i] ); if ( "value" in desc ) { desc.writable = false; } desc.configurable = false; Object.defineProperty( obj, props[i], desc ); } return Object.preventExtensions( obj ); };[/js] Freezing an object is the ultimate form of lock-down. Once an object has been frozen it cannot be unfrozen - nor can it be tampered in any manner. This is the best way to make sure that your objects will stay exactly as you left them, indefinitely. All together these changes are very exciting, they provide you with an unprecedented level of control over the objects that you produce. The best aspect, though, is that you will be able to use these features to build larger and more complex features in pure ECMAScript (such as building new DOM modules, or moving more browser APIs into pure-JavaScript). And since all the browsers are on board this is absolutely something that we can look forward to.
Chris Maritn (May 21, 2009 at 1:49 am)
I see some nice stuff but, I also see a lot of “backward compatibility” stuff. That’s unfortunate IMO. It should be up to the browser to allow old code or not.
It’s not until that happens that the web will “really” move forward.
ken (May 21, 2009 at 2:15 am)
Any word on when FF might adapt this?
Andrea Giammarchi (May 21, 2009 at 3:36 am)
half of this stuff is already implemented in vice-versa but as Chris said something is not possible to implement at all.
Maybe getOwnPropertyDescriptor could be fake and always return an object true,true,true but in this case I miss the sense.
Finally, I love the first sentence: Rising from the ashes of ECMAScript 4 … so can we call this version JS Phoenix ? Too cool :D
Elijah Grey (May 21, 2009 at 5:46 am)
I (partially, doesn’t support
false
forwritable
,enumerable
, orconfigurable
) implemented Object.defineProperty and Object.getOwnPropertyDescriptor called Xccessors. I also have a different version of Xccessors that implements the legacy methods__(lookup|define)[GS]etter__
in IE8.Nicolas (May 21, 2009 at 5:59 am)
Very interesting. What about performance?
Dealing with Freeze/Sealed objects has (or maybe should have) some performance improvements over manipulating mutable objects?
John Resig (May 21, 2009 at 7:21 am)
@Chris Martin: Unfortunately we’re pretty much stuck with the code that we have – and browsers have the be backwards compatible going forward if they wish to keep users (no users will use a browser that breaks their favorite web sites). There’s a reason why backwards compatibility was baked into the specification – it’s because all the browser vendors wanted it that way!
@ken: No word on this yet. I hope soon!
@Andrea: Looking at the source of vice-versa, the only new method that you appear to implement (sort-of) correctly is Object.keys. Of course, that makes sense because all the other methods shown above require other new features provided by the browser.
I do agree that a lot of the stuff could be faked right now (having the attributes always return true) but that seems like a cheap shot.
@Nicolas: Not sure about performance, yet – but I’m sure there’s bound to be some benefits to dealing with an immutable object.
William J. Edney (May 21, 2009 at 7:43 am)
John –
Should the JavaScript version of the ‘Object.keys()’ implementation that you show above have a ‘hasOwnProperty()’ check?
15.2.3.14 (page 109) talks about “own” enumerable properties…
Cheers,
– Bill
John Resig (May 21, 2009 at 9:00 am)
@William: Good catch, fixed the example.
phifeshaheed (May 21, 2009 at 9:27 am)
Will Javascript implements that standard? Flex did implement the latest of the ECMA version but JS is always one train behind, so will it be of any concern for a JS developper that this ECMA5 is released?
Elijah Insua (May 21, 2009 at 9:42 am)
Very cool, I can’t wait till this is available!
John Resig (May 21, 2009 at 9:44 am)
@phifeshaheed: You may be slightly confused. ActionScript (which is used in Flex and Flash) implements an older version of the ECMAScript 4 specification. They’re the only major vendor to do so (with the exception of JScript.NET, which isn’t in Internet Explorer, unless you count Silverlight). As it stands, no major browser vendor is planning on implementing the features in ECMAScript 4 – however all of them are planning on implementing ECMAScript 5. This is an important distinction going forward.
I should mention that it’s very likely that the ActionScript team will implement the features from ECMAScript 5 in a way that’s compatible with their implementation.
Raphael Speyer (May 21, 2009 at 10:12 am)
I’m working on adding EcmaScript 5 features to the Rhino Javascript engine as part of a Google Summer of Code project. It’s very much in a state of flux at the moment, but the code’s on github for those interested in watching/forking/contributing.
http://github.com/rapha/rhino
If all goes to plan, at least a significant portion of the work should be complete by mid-August.
grayger (May 21, 2009 at 10:24 am)
After an object was sealed or frozen, is it possible to unseal or unfreeze it? I think it should not be allowed.
Dougal Campbell (May 21, 2009 at 10:26 am)
So, would you be able to do something like “Object.seal( document )” for a quick-n-dirty greasemonkey browser security hack?
I hope the browsers don’t let you freeze() the window object :)
John Resig (May 21, 2009 at 10:39 am)
@Raphael Speyer: I’m looking forward to seeing an implementation! It’ll be especially useful in Rhino, where we can use it for projects like Env.js.
@grayger: No, you will not be able to unseal or unfreeze an object or property once it is “locked” – it’s that way for as long as the object exists. This is done intentionally in order to prevent other code from unlocking it and then modifying it.
@Dougal Campbell: Good question, I’m not sure! I especially wonder about the case of
Object.freeze( document.body )
– would it prevent you from being able to append to elements to the body (since lastChild would need to be updated in order to have that work).I’m going to guess that built-in methods will have the descriptor of:
{writable: true, enumerable: false, configurable: false }
(and possibly writable: false, for cases like lastChild) – which means that you won’t be able to change the descriptors of those properties later on, which means that freezing or sealing those objects won’t work.Myk Melez (May 21, 2009 at 11:07 am)
I wish there would be syntactic sugar for configuring properties when defining objects.
zimbatm (May 21, 2009 at 12:36 pm)
While those additions are certainly worthwile, they’re, again, extending the core language.
I’d better like a minimalist approach, which can then be extended to the language you’d like to have.
For that, you would need two things:
* A way to store “languages” in your browser, for example by re-using url caches.
* A way to extend the language safely by adding a new mean of encapsulation which does not capture context like functions.
JSON could really just be a namespace, which would be a subset of the javascript language.
ns = new Namespace();
ns.hide(Function);
// ...
ns.load("http://json-language.ore/impl/v1.js")
ns.eval("{your:'json';object:null}");
Azat Razetdinov (May 21, 2009 at 1:43 pm)
Am I the only one confused with the mixed style in method naming?
Object.keys(object)
Object.getOwnPropertyNames(object)
Object.getPrototypeOf(object)
Axel Rauschmayer (May 21, 2009 at 2:43 pm)
Will we still get optional typing at some point in the future (as was planned for ECMAScript 4) or has that feature been canceled?
Nate (May 21, 2009 at 4:24 pm)
If we copy an object will the newly added security properties be copied too?
If writable is false, does that only mean we cannot add to the object, or will it also prevent us from overwriting the object?
Here is some code that is probably not quite right, but will hopefully hint at what I’m getting at:
obj.name = "John";
Object.preventExtensions( obj );
print( Object.isExtensible( obj ) ); //false
var copy = obj;
if ( !Object.isExtensible( copy ) ) {
copy = {};
var ignore = {writable:true, enumerable:true, configurable:true, extensible:true };
for(p in obj) {
if(ignore[p]) { continue; }
copy[p] = obj[p];
}
obj = copy;
Object.isExtensible( obj );
Object.isExtensible( copy );
}
What would happen in the above code?
Elijah Grey (May 21, 2009 at 7:40 pm)
@zimbatm: The
Namespace()
(and similarQName()
) are already used in E4X for XML namespaces. Namespaces are created in a completely different syntax introduced in ECMAScript 4.skierpage (May 21, 2009 at 8:29 pm)
Would someone please abuse ECMA for publishing this spec as a 3.4 MB browser-locking CPU-hogging PDF to whose sections (“pages 107-109” for the loss) you can’t link and which doesn’t even have inner linking from its TOC or internal section references?!
“Creator: Microsoft(R) Office Word 2007” — obviously, use anything but Word to generate PDFs.
Nicholas C. Zakas (May 21, 2009 at 10:08 pm)
Back when this was called “ECMAScript 3.1”, I did a reference implementation to play around with some of these as well: http://www.nczonline.net/blog/2008/11/09/ecmascript-31-static-object-methods-prototype/. It’s very helpful to test out how you think things might work.
Raphael Speyer (May 22, 2009 at 1:20 am)
@Nate
Here’s what I think is going on.
obj.name = "John";
Object.preventExtensions( obj );
print( Object.isExtensible( obj ) ); //false
var copy = obj;
You haven’t actually copied the object here, you now just have two references to it.
writable, enumerable and configurable are attributes of a *property*, extensible is an attribute of an *object*, but it’s not accessible as such, you can simply call use the Object.isExtensible and Object.preventExtensions methods.
if ( !Object.isExtensible( copy ) ) {
copy = {};
var ignore = {writable:true, enumerable:true, configurable:true, extensible:true };
for(p in obj) {
if(ignore[p]) { continue; }
copy[p] = obj[p];
}
obj = copy;
Object.isExtensible( obj );
Object.isExtensible( copy );
}
What would happen in the above code?
Here obj and copy both now refer to your newly created object (with {}) so and they’ll both be extensible (by default).
To copy an object more completely you could do:
var copy = Object.create(Object.getPrototypeOf(obj), {})
for (var p in Object.getOwnPropertyNames(obj) {
var descriptor = Object.getOwnPropertyDescriptor(obj, p)
Object.defineProperty(copy, p, descriptor)
}
if (!Object.isExtensible(obj))
Object.preventExtensions(copy);
hannesw (May 22, 2009 at 1:34 am)
I think you got the default value for the writable, enumerable, configurable attributes in property descriptors wrong. Have a look at table 3 in section 8.6.1 of the candidate draft,they’re listed as false.
Andrey Shchekin (May 22, 2009 at 3:56 am)
Seal/freeze/preventExtensions looks good if the javascript framework authors never make mistakes or all js frameworks were easily editable. I have used extensibility very often to work around bad framework design.
Christian (May 22, 2009 at 7:01 am)
Lots of good stuff here, but I don’t really care for
Object.getOwnPropertyDescriptor(obj, prop)
as opposed to:
obj.getOwnPropertyDescriptor(prop)
Same goes for all the new Object stuff. What’s the reasoning for not implementing this on the prototype?
Andrea Giammarchi (May 22, 2009 at 9:01 am)
John, in vice-versa defineProperty works fine in FireFox, Opera, Chrome, and Safari. It is not fully compatible, even native, in IE8 while is not implementable in IE
Raphael Speyer (May 22, 2009 at 12:39 pm)
@hannesw
I was quoting Nate’s code. I think he thought that
writable
,configurable
,enumerable
andextensible
were all enumerable properties of theobj
object, and he didn’t want to copy them.hannesw (May 22, 2009 at 3:17 pm)
Raphael: My comment was referring to John’s original article, more specifically the following sentence: “The three attributes (writable, enumerable, and configurable) are all optional and all default to true.” Which in my (shallow) understanding of ES5 is wrong. Sorry for the confusion.
Hedger Wang (May 22, 2009 at 3:37 pm)
This would be definitely useful, and the browser support isn’t that really important. What’s important is the standard SPEC that major vendor would agree with.
For a large JavaScript application, most JavaScript codes are actually compiled and checked on the server side first and these new features make it really easy to spot the potential system errors from the codes and it does force good control of codes for developers.
In my opinion, Rhino should implement these features ASAP.
Thanks.
Nate (May 22, 2009 at 5:11 pm)
@Raphael: Thanks for clearing up some issues with the code and my word choices. I knew there were problems with it, but I just threw it together quickly in an attempt to illustrate what I was trying to get at.
Basically, what I was trying to get at was, if a programmer doesn’t set enumerable to false an object can be copied and overwritten allowing anyone to work with objects just as openly as they do now. Is this correct?
David Flanagan (May 22, 2009 at 6:06 pm)
Thanks for the summary, John.
The thing I like about Object.keys is that it provides an alternative to for/in looping that ignores inherited properties:
Object.keys(o).forEach(function() {...})
Also, the
Object.create()
function (minus the properties stuff) can be defined in ES3. Douglas Crockford does it like this:Object.create = function(proto) {
var F = function(){};
F.prototype = proto;
return new F();
};
Kris Kowal (May 23, 2009 at 6:45 pm)
One more edge case on
Object.keys
: Instead ofobj.hasOwnProperty(name)
, it needs to beObject.prototype.hasOwnProperty.call(obj, prop)
so that the “hasOwnProperty” key gets handled correctly if it exists. Guess who’s got two thumbs and is excited that this will be part of the language instead of a rite of passage!Jakub Kopec (May 24, 2009 at 6:12 am)
@Andrey Shchekin:
You wrote “Seal/freeze/preventExtensions looks good if the javascript framework authors never make mistakes or all js frameworks were easily editable. I have used extensibility very often to work around bad framework design.”
This is exactly the thing that I first thought of when I first saw ‘freeze’ ideas!
I think that what we’re seeing now is a case of pushing to JS ideas that will benefit only framework writes but make humble devs lives more difficult.
@Dougal Campbell:
You wrote: So, would you be able to do something like “Object.seal( document )” for a quick-n-dirty greasemonkey browser security hack? I hope the browsers don’t let you freeze() the window object :)
And this is exactly the thing I fear the most. The specification is about the language. The DOM behavior in regards to this will be defined by whom? By consensus? This ‘freeze’ thing may very quickly raise some new obstacles to everyone that tinkers with the web.
By the way, I was looking at the ‘freeze’ family with disgust for some time, but after your comment my attitude changes to “please take this horror away from me”.
@ejohn:
You wrote (in blog post): “Property descriptors (and their associated methods) is probably the most important new feature of ECMAScript 5. It gives developers the ability to have fine-grained control of their objects, prevent undesired tinkering, and maintaining a unified web-compatible API.”
Is there something like “desired tinkering”?
For all of you reading this: please think for the moment about all moments in your life in which you wanted to tinker with something (code, physical object), but it was not possible/very hard, because somebody locked the thing tight.
Ask yourself the question: would you like more of this? Or maybe less?
For me it is obvious that freeze/seal/preventExtensions MUST BE reversible. In principle, controlling access should be treated as hint not as law. If one wants to tinker – let’em.
Of course I understand points of those happy with this new, cold, additions. The “well designed” interfaces. The code/data access control. The constant fear that the user of my code can tinker with my objects (that’s horrible, really :)
Make those reversible, that is the only way to stop JS from becoming yet another “mature” language which favors library creators over library users.
MySchizoBuddy (May 25, 2009 at 12:07 pm)
When will javascript run off of GPU? just like the incoming OpenCL
Corwin (May 25, 2009 at 5:26 pm)
I second Jakub. Fixing something in somebody else’s code is everyday stuff. Taking away this “No worries, can fix everything, it’s JS” mantra will in no doubt result in some nerve-wrecking moments.
Steven Wood (June 1, 2009 at 8:02 am)
I think Object.create() is just a variation of (or implementation of) Crockford’s “beget” method that he uses frequently in his “JavaScript: The Good Parts book”.
Dojo also has this – they call it “dojo.delegate”. It’s cross borwser implementaion is simply :
Object.prototype.create = function (object, props) {
var F = function {};
F.prototype = object;
var newObj = new F();
// copy properties from props to newObj
// …
return newObj;
};
Personally I prefered his “beget” convention which didn’t require the object 1st argument and operated on “this”. I think it makes more sense to write :
var manager = employee.beget();
@Jakub – If you don’t like a framework object that is sealed, you should be able to change it globally ? e.g. if I didn’t like dojo.delegate, but dojo was sealed couldn’t I do :
window.dojo = Object.create(dojo);
dojo.delegate = function () {// whatever…}
Although thinking about it this appears to totally undermine seal() ?
jor (June 5, 2009 at 12:41 pm)
Nice extensions to the JavaScript language.
However, one very simple thing I’d like to see in the browser is a simple RegExp.escape() function.
I think its place is in the core JS language, not in libraries, because the browser knows better what characters should be escaped.
Cynic (July 21, 2009 at 3:43 pm)
The only good part of JavaScript is that nearly everything is extensible. Please don’t take that away.