Saturday, April 21, 2012

Enum - Enumerated Type

In this post, we're going to look at the enumerated type or enum, as it might exist in Javascript and how one might use an enumerated type in a program. 


Interestingly,  'enum' is a reserved word, which is an indicator of its importance as a programming concept and its possible inclusion as a standard type in future versions of the Javascript language. You can try the examples in this post by cutting and pasting to here.

Saturday, April 14, 2012

Object: Data Property and Accessor Property

This post discusses the anatomy of object properties. In particular we focus on Data and Accessor Properties. 
Using literal object notation in code examples, we'll explore exceptional cases in property attribute defaults and their comparison with Object property creation methods such as Object.defineProperty( .. )
Specifically, we'll compare default property attribute values, when creating literal objects, to those created through Object creation methods.
  • Literal Object Properties - set attributes writable, enumerable and configurable to true by default. 
  • Object creation methods - set attributes writable, enumerable and configurable to false by default. 
We also attempt to show a legitimate use of Accessor Properties, which improve the robustness of code.


Related material we won't cover: Internal object properties; object creation methods; this binding and method invocation rule; prototypal inheritance.


Object

In Javascript, an Object is considered to be an unordered set of properties, similar to a HashMap.
Using literal object notation, we would write an object in the following manner:
 var a = {  
  b: '1',  
  c: true,  
  d: 'dee'  
 };  
And access an object's properties using dot notation, like so:
 var e = a.d; // use dot notation to access object properties  
Or bracketed notation like so:
 var e = a['d'];  

Property

A property is a name (string identifier) associated with a  property descriptor.


Property Descriptor

Attributes are used to define the state of properties. These attributes are available as fields on a Property Descriptor object. Fields vary, depending on property descriptor type however, in general, the following fields are available: 
  • value 
  • get 
  • set 
  • writable 
  • configurable 
  • enumerable. 
We'll discuss this further in the Property Descriptor Types section below.


Types of Properties

As of ECMAScript 5, three property types are available:
  • Internal Properties
  • Data properties 
  • Accessor properties 
Here, we'll focus on Data and Accessor Properties.


Data Properties

The Data Property directly associates a name to a value through the value field of the Property Descriptor. 
When a Data Property's descriptor value directly references a Function type, it is referred to as a method.
Data Properties are defined like so:
 var james = {  
    _name: 'james',  
    getName: function() {  // method
      return this._name;  
    }
 };  
 writeln('You can call me ' + james.getName()); // You can call me james  


Accessor Properties

Accessors are something new to ECMAScript 5. They are described through user provided getter and setter functions. Where the user provided functions are assigned to get and set attributes on the Accessor Property's property descriptor object.
Accessor Properties are defined like so:
1:  var james = {  
2:   _name: 'james',  
3:   get name() {  
4:     return this._name;  
5:   },  
6:   set name(aName) {  
7:     this._name = aName;  
8:   }  
9:  };  
10:  james.name = 'Jimmy';  
11:  writeln('You can call me ' + james.name); // You can call me Jimmy  
Notice how the accessor property is used in lines 10 and 11. The getter-setter accessor functions are hidden from the syntax by indirectly accessing the name value through the get and set fields of the Accessor Property Descriptor.


Property Descriptor Types

You can determine the Property type based upon the existence or use of certain fields on the Property Descriptor object. 
For example - A data property descriptor is one that includes any fields named either [[Value]] or [[Writable]]. The tables below shows the descriptor attributes for both Property Descriptor types. Rows in green identify attributes used to categorise a descriptor.


Data Property Descriptor Attributes

An accessor property descriptor includes any fields named either [[Get]] or [[Set]].


Accessor Property Descriptor Attributes

Note

If you add a property without using one of:
  • Object.defineProperty 
  • Object.defineProperties 
  • Object.create 

The writable, enumerable, and configurable attributes are all set to true.

So when you create an object using literal notation: 
  var image = {   
     _imageName: undefined,   
     set name(imageName) {   
        this._imageName = imageName;   
     },   
     get name() {   
        return this._imageName;   
     }   
  }  

Properties _imageName and name look like this:




Playing with Property Types and Attributes


Lets start with some requirements:
  • We need an image object that holds name and image size information. 
  • Size must be numeric and is assumed to be in bytes. If not, it should throw an invalid data error.
  • Image name must end in one of the following ( jpg|gif|png ). If not, it should throw an invalid image type error.
  • When properties are enumerated, we should only see size and name. 
  • These properties must exist on an image. 

To create or modify properties on an object, use:


defineProperty(.. )  - defines a new property directly on an object, or modifies an existing property on an object, and returns the object.
The example below is not an ideal way to define an object and it's properties, however through the following contrived method, I hope to highlight various rules around property creation and attribute settings.
1:    var image = {    
2:     _imageName: undefined,    
3:     set name(imageName) {    
4:       this._imageName = imageName;    
5:     },    
6:     get name() {    
7:       return this._imageName;    
8:     }    
9:    };  
10:   writeln('Enumerable Properties of image are:');   
11:   for(var p in image) {   
12:     writeln(' * ' + p);   
13:   }   
Which outputs:
1:  Enumerable Properties of image are:  
2:   * _imageName  
3:   * name  

We started by using the image  object, defined earlier using literal notation, as a template. 
In doing so, writable, enumerable and configurable attributes, on _imageName Data Property Descriptor were defaulted to true. 
We also defined an Accessor Property - name. It's enumerable and configurable attributes were also defaulted to true. 
In both cases, this was because we used literal notation instead of the Object creation methods such as  defineProperty(.. ) .


We then checked to see which properties were enumerable. According to the requirements, _imageName is not to be included in the enumerated property set.
To fix this, we need to modify the enumerable attribute on _imageName.
1:   var imageNameDataDescriptor = {   
2:      enumerable: false  
3:   };  
4:   Object.defineProperty(image, '_imageName', imageNameDataDescriptor);   
5:   writeln('Enumerable Properties of image are:');   
6:   for(var p in image) {   
7:     writeln(' * ' + p);   
8:   }   
Which outputs:
 Enumerable Properties of image are:  
  * name  

The requirements also state that name is a mandatory property of image - meaning you can't delete it, prevent it from being enumerable, or change it to a Data Property Descriptor. However, we would like to be able to change it's value.
These requirements also imply that _imageName should also be constrained in a similar manner since it is the storage property for the accessor value.

1:   Object.defineProperty(image, '_imageName', {configurable: false});  
2:   Object.defineProperty(image, 'name', {configurable: false});   
3:   var question = '(true or false) I can still delete ';  
4:   writeln(question + 'image.name: ' + (delete image.name));  
5:   writeln(question + 'image._imageName: ' + (delete image._imageName));  
Which outputs:
 (true or false) I can still delete image.name: false  
 (true or false) I can still delete image._imageName: false  

So what have we achieved?

  1. made sure the Data Property is not enumerable, only the Accessor Property remains enumerable.
  2. made sure you can't delete our properties or modify their attributes. However, you can still change their values.
Let's do the same for size property, as stipulated in the requirements. 
Instead of recreating the image object, we'll use defineProperty(.. ) to create the size Accessor Property and its associated Data Property for storage of the image size value.
1:   var sizeInBytesDataDescriptor = {   
2:     value: 0   
3:    },   
4:    sizeAccessorDescriptor = {   
5:     set: function(sizeInBytes) {   
6:       this._sizeInBytes = sizeInBytes;   
7:     },    
8:     get: function() {   
9:       return this._sizeInBytes;   
10:     }   
11:    };  
12:    Object.defineProperty(image, '_sizeInBytes', sizeInBytesDataDescriptor);  
13:    Object.defineProperty(image, 'size', sizeAccessorDescriptor);  
14:    writeln('Enumerable Properties of image are:');   
15:    for(var p in image) {   
16:    writeln(' * ' + p);   
17:    }   
18:    image.size = 50000;  
19:    writeln('image size is: ' + image.size);   
Which outputs:

 Enumerable Properties of image are:  
  * name  
 image size is: 0  
Ok, so that didn't work out as expected. We want size property to be enumerable and we'd also like to be able to change the size value, as we attempted to in line 18.

Why didn't this work? The answer lies in the way we created the properties. Up until now, we've used literal object notation, which defaults writable, enumerable and configurable to true.
We're now using Object.defineProperties, which defaults the same attributes to false. Pay attention to this difference if you're mixing your property construction on objects. We  can attempt to fix this by doing the following:

1:    Object.defineProperty(image, '_sizeInBytes', {writable: true});  
2:    Object.defineProperty(image, 'size', {enumerable: true});  
Which outputs:
 TypeError : Cannot redefine property: _sizeInBytes  
 TypeError : Cannot redefine property: size  
We've hit a dead-end. We have two properties that have their configurable attribute set to false. As a result, any attempt to fix this results in a type error.

Lets start again, taking what we've learned so far. We'll also add code for the final two requirement tasks:


  1. Check size value is always numeric and throw a type error when its not. 
  2. Check name value extension is one of (.jpg, .gif, .png ) and throw a type error if its not.

1:   var isValidImageName = new RegExp(/\.(?:jpg|gif|png)$/i),  
2:   isInteger = new RegExp(/^\s*(\+|-)?\d+\s*$/),  
3:   image = {   
4:    _imageName: undefined,  
5:    _sizeInBytes: 0,   
6:    set name(imageName) {    
7:     if(!isValidImageName .test(imageName)) {  
8:      throw new Error('Image [ ' + imageName + ' ] must be one of(*.jpg, *.gif,*.png)');  
9:     }  
10:     this._imageName = imageName;   
11:    },   
12:    get name() {   
13:     return this._imageName;   
14:    },  
15:    set size(sizeInBytes) {  
16:     if(!isInteger.test(sizeInBytes)) {  
17:      throw new Error('Image size in bytes [ ' + sizeInBytes+ ' ] must be an integer');  
18:     }  
19:     this._sizeInBytes = Math.abs(parseInt(sizeInBytes, 10));  
20:    },  
21:    get size() {  
22:     return this._sizeInBytes;  
23:    }   
24:   },  
25:   imageNameDataDescriptor = {  
26:    enumerable: false,  
27:    configurable: false  
28:   },  
29:   nameAccessorDescriptor = {  
30:    configurable: false  
31:   },  
32:   sizeInBytesDataDescriptor = {  
33:    enumerable: false,  
34:    configurable: false  
35:   },  
36:   sizeAccessorDescriptor = {  
37:    configurable: false  
38:   };  
39:   Object.defineProperty(image, '_imageName', imageNameDataDescriptor);  
40:   Object.defineProperty(image, 'name', nameAccessorDescriptor);  
41:   Object.defineProperty(image, '_sizeInBytes', sizeInBytesDataDescriptor);  
42:   Object.defineProperty(image, 'size', sizeAccessorDescriptor);  
43:   writeln('TEST: Only Accessor Properties are enumerable\n');  
44:   for(var p in image) {  
45:    writeln(' * ' + p);  
46:   }  
47:   writeln('\nTEST: Setting size and name property values\n');  
48:   image.name = 'funny.jpg';  
49:   image.size = 42238;  
50:   writeln('image name: ' + image.name + ' and size: ' + image.size + ' bytes');  
51:   writeln('\nTEST: Error thrown when attempt to set an invalid image name.\n');  
52:   try {  
53:    image.name = 'invalidName.bmp'; // expect error for invalid image name  
54:   }catch(err){  
55:    writeln(err);  
56:   }  
57:   writeln('\nTEST: Error thrown when attempt to set image size to alpha value.\n');  
58:   try {  
59:    image.size = '50kb'; // expect error for non-numeric value  
60:    writeln('image.size = ' + image.size);  
61:   }catch(err){  
62:    writeln(err);  
63:   }  
64:   writeln('\nTEST: negative size values should be converted to absolute values.\n');  
65:   image.size = -50000; // expect error for negative value  
66:   writeln('image.size = ' + image.size);  
Which outputs:
 TEST: Only Accessor Properties are enumerable  
  * name  
  * size  
 TEST: Setting size and name property values  
 image name: funny.jpg and size: 42238 bytes  
 TEST: Error thrown when attempt to set an invalid image name.  
 Error: Image [ invalidName.bmp ] must be one of(*.jpg, *.gif,*.png)  
 TEST: Error thrown when attempt to set image size to alpha value.  
 Error: Image size in bytes [ 50kb ] must be an integer  
 TEST: negative size values should be converted to absolute values.  
 image.size = 50000  
That's much better. We now have an image object that will always have name and size properties. We also have property checks through Property Accessor setters to ensure that the data assigned is correct format.


To avoid confusion with syntax, note that, when creating Accessor Properties, we employed two styles. The first used literal notation on the object property. We used set and get keywords in front of the property name to indirectly manipulate the get and set attributes on the properties Descriptor Property - like so:

1:  var james = {  
2:   _name: 'james',  
3:   get name() {  
4:     return this._name;  
5:   },  
6:   set name(aName) {  
7:     this._name = aName;  
8:   }  
9:  };    

The other was to directly create a property descriptor object, again using literal notation and assign to the property using Object.defineProperty( .. ). In the case of the property descriptor object; set and get are plain old method properties, albeit constrained by rules:

  • set and get properties must be assigned to a Function type
  • set function can only take one parameter
  • get function does not take parameters

As mentioned earlier, the interpreter determines the property type (Data or Accessor) by the attributes defined on its associated Property Descriptor.
  • If a property has a Property Descriptor with a value and writable attributes, it's considered a Data Property.
  • If a Property has a Property Descriptor with set and get attributes, it's considered an Accessor Property.
 Mixing these attributes, when defining a literal Property Descriptor, results in a Type Error.
 var car = {};  
 Object.defineProperty(car, 'doors', {value:2,   
                            get: function() {  
                              return 'wtf';  
                            }});  
Which outputs:
 TypeError: Invalid property. A property cannot both have accessors and be writable or have a value, #<Object>  




Closing
Thanks for sticking with it. I know this post was long.
In this post, we established a definition for an object, its properties and associated Property Descriptors. 
We then explored the use of Accessor methods and Property Descriptor attributes while creating an image object, which will always guarantee it has name and size properties with valid values. Guarantees like this improve maintainability and extend the life of your code. 


What we know
  • An object has properties
  • Properties are a name associated to a property descriptor
  • Property Descriptors are objects with fields representing attributes of a property. Those fields vary, depending on the Property Descriptor type.
  • If a property has a Property Descriptor with a value and writable attributes, it's considered a Data Property.
  • If a Property has a Property Descriptor with set and get attributes, it's considered an Accessor Property.
  • Properties created using object creation methods have boolean defaults set to false
  • Properties created using an alternative method have boolean defaults set to true



Further Reading