ECMAScript 5 Objects and Properties


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:

  1. var obj = {};
  2.  
  3. obj.name = "John";
  4. print( obj.name );
  5. // John
  6.  
  7. print( Object.isExtensible( obj ) );
  8. // true
  9.  
  10. Object.preventExtensions( obj );
  11.  
  12. obj.url = "http://ejohn.org/"; // Exception in strict mode
  13.  
  14. print( Object.isExtensible( obj ) );
  15. // 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:

  1. {
  2.   value: "test",
  3.   writable: true,
  4.   enumerable: true,
  5.   configurable: true
  6. }

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:

  1. var obj = { foo: "test" };
  2.  
  3. print(JSON.stringify(
  4.   Object.getOwnPropertyDescriptor( obj, "foo" )
  5. ));
  6. // {"value": "test", "writable": true,
  7. //  "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:

  1. var obj = {};
  2.  
  3. Object.defineProperty( obj, "value", {
  4.   value: true,
  5.   writable: false,
  6.   enumerable: true,
  7.   configurable: true
  8. });
  9.  
  10. (function(){
  11.   var name = "John";
  12.  
  13.   Object.defineProperty( obj, "name", {
  14.     get: function(){ return name; },
  15.     set: function(value){ name = value; }
  16.   });
  17. })();
  18.  
  19. print( obj.value )
  20. // true
  21.  
  22. print( obj.name );
  23. // John
  24.  
  25. obj.name = "Ted";
  26. print( obj.name );
  27. // Ted
  28.  
  29. for ( var prop in obj ) {
  30.   print( prop );
  31. }
  32. // value
  33. // name
  34.  
  35. obj.value = false; // Exception if in strict mode
  36.  
  37. Object.defineProperty( obj, "value", {
  38.   writable: true,
  39.   configurable: false
  40. });
  41.  
  42. obj.value = false;
  43. print( obj.value );
  44. // false
  45.  
  46. 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:

  1. Object.defineProperties = function( obj, props ) {
  2.   for ( var prop in props ) {
  3.     Object.defineProperty( obj, prop, props[prop] );
  4.   }
  5. };

Example Usage:

  1. var obj = {};
  2.  
  3. Object.defineProperties(obj, {
  4.   "value": {
  5.     value: true,
  6.     writable: false
  7.   },
  8.   "name": {
  9.     value: "John",
  10.     writable: false
  11.   }
  12. });

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:

  1. Object.keys = function( obj ) {
  2.   var array = new Array();
  3.   for ( var prop in obj ) {
  4.     if ( obj.hasOwnProperty( prop ) ) {
  5.       array.push( prop );
  6.     }
  7.   }
  8.   return array;
  9. };

Example Usage:

  1. var obj = { name: "John", url: "http://ejohn.org/" };
  2.  
  3. print( Object.keys(obj).join(", ") );
  4. // 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)

  1. Object.create = function( proto, props ) {
  2.   var ctor = function( ps ) {
  3.     if ( ps )
  4.       Object.defineProperties( this, ps );
  5.   };
  6.   ctor.prototype = proto;
  7.   return new ctor( props );
  8. };

Other implementation:

  1. Object.create = function( proto, props ) {
  2.   var obj = new Object();
  3.   obj.__proto__ = proto;
  4.  
  5.   if ( typeof props !== "undefined" ) {
  6.     Object.defineProperties( obj, props );
  7.   }
  8.  
  9.   return obj;
  10. };

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 method Object.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:

  1. function User(){}
  2. User.prototype.name = "Anonymous";
  3. User.prototype.url = "http://google.com/";
  4.  
  5. var john = Object.create(new User(), {
  6.   name: { value: "John", writable: false },
  7.   url: { value: "http://google.com/" }
  8. });
  9.  
  10. print( john.name );
  11. // John
  12.  
  13. 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:

  1. Object.seal = function( obj ) {
  2.   var props = Object.getOwnPropertyNames( obj );
  3.  
  4.   for ( var i = 0; i < props.length; i++ ) {
  5.     var desc = Object.getOwnPropertyDescriptor( obj, props[i] );
  6.    
  7.     desc.configurable = false;
  8.     Object.defineProperty( obj, props[i], desc );
  9.   }
  10.  
  11.   return Object.preventExtensions( obj );
  12. };

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:

  1. Object.freeze = function( obj ) {
  2.   var props = Object.getOwnPropertyNames( obj );
  3.  
  4.   for ( var i = 0; i < props.length; i++ ) {
  5.     var desc = Object.getOwnPropertyDescriptor( obj, props[i] );
  6.    
  7.     if ( "value" in desc ) {
  8.       desc.writable = false;
  9.     }
  10.    
  11.     desc.configurable = false;
  12.     Object.defineProperty( obj, props[i], desc );
  13.   }
  14.  
  15.   return Object.preventExtensions( obj );
  16. };

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.

Posted: May 21st, 2009


If you particularly enjoy my work, I appreciate donations given with Gittip.

40 Comments (Show Comments)



Comments are closed.
Comments are automatically turned off two weeks after the original post. If you have a question concerning the content of this post, please feel free to contact me.


Secrets of the JavaScript Ninja

Secrets of the JS Ninja

Secret techniques of top JavaScript programmers. Published by Manning.

Ukiyo-e Database and Search

Ukiyo-e.org

Japanese woodblock print database and search engine.


John Resig Twitter Updates

@jeresig

Infrequent, short, updates and links.


via Ad Packs