In ECMAScript 5 we now have two distinct kinds of properties.
- Data properties
- Accessor properties
A property is a named collection of attributes.
value: any JavaScript value
writable: boolean
configurable: boolean, common for both Data and Accessor
enumerable: boolean, common for both Data and Accessor
get: a function that returns a value
set: a function that takes an argument as its value
configurable
Any attempts to delete the property or change its (writable, configurable, or enumerable) attributes will fail if set to false.
if using strict mode, we get a run time error.
if not using strict mode, the behaviour is as it was with ES3,
the deletion attempt is ignored.
If set to false:
-It can not be re-set to true.
-We can change the value and writable attributes, but writable only from true to false.
enumerable
The property will be enumerated over when a for-in loop is encountered if set to true.
if using strict mode, it’s as if the property doesn’t exist, it’s ignored.
In ES5,
- a default property descriptor; if the property is defined the old fashioned way, without using
Object.defineProperty
,
the boolean attributes will all default to true. - A default property descriptor; if the property is defined using
Object.defineProperty
and the boolean attribute values not specified,
the boolean attributes will all default tofalse
.
I was wondering about this, as I had heard conflicting stories.
IMO this follows the Principle of least astonishment (POLA)
var obj1 = {}; var obj1PropertyDesc; var obj2 = {}; var obj2PropertyDesc; Object.defineProperty(obj1, 'propOnObj1', { value: 'value of propOnObj1' //, // writable: false, // enumerable: false, // configurable: false, }); obj1PropertyDesc = Object.getOwnPropertyDescriptor(obj1, 'propOnObj1'); // obj1PropertyDesc { // configurable: false, // enumerable: false, // value: "value of propOnObj1", // writable: false // } obj2.propOnObj2 = 'value of propOnObj2'; obj2PropertyDesc = Object.getOwnPropertyDescriptor(obj2, 'propOnObj2'); // obj2PropertyDesc { // configurable: true, // enumerable: true, // value: "value of propOnObj2", // writable: true // }
So in general
Properties declared the old ES3 way are configurable
(can be deleted).
Properties declared using Object.defineProperty;
by default are not configurable
(can not be deleted).
See edge cases below.
The delete
operator is used to remove a property from an object.
It does not touch properties in the prototype chain.
If you have a prototype that has a property with the same name, it will now be used when your code references the derived object’s property that no longer exists.
var objLiteral = { aProperty: 'value of super property' } var anObject = Object.create(objLiteral); // create is an ES5 method, but easy enough to replicate for ES3 implementations anObject.aProperty = 'value of derived property'; anObject.aProperty // 'value of derived property' delete anObject.aProperty; anObject.aProperty // 'value of super property'
Edge cases
JavaScript Patterns pg 12 states “Implied globals created without var (regardless if created inside functions) can be
deleted.”
Thanks to Angus Croll for pointing this out as untrue.
obj1 = 'kims global property'; var obj1PropertyDesc; var obj2PropertyDesc; obj1PropertyDesc = Object.getOwnPropertyDescriptor(this, 'obj1'); // obj1PropertyDesc { // configurable: true, // enumerable: true, // value: "kims global property", // writable: true, // } (function (){ obj2 = 'kims global property declared within function scope'; }()); obj2PropertyDesc = Object.getOwnPropertyDescriptor(this, 'obj2'); // obj2PropertyDesc { // configurable: false, // enumerable: true, // value: "kims global property declared within function scope", // writable: true // } delete obj2; // Nope, obj2 was not deleted. // turn strict mode on and we get the following error: // Uncaught SyntaxError: Delete of an unqualified identifier in strict mode.
–
When you declare a global,
you are actually defining a property of the global object.
If you use the var
keyword on that global, you are still creating a property.
That property is non-configurable (can not be deleted with the delete operator).
Only object properties with the configurable
option set to true can be deleted.
Nothing else can be deleted.
Variables which are properties that we can’t access their property descriptor, can never be deleted.
var obj1 = {}; var obj1PropertyDesc; obj1PropertyDesc = Object.getOwnPropertyDescriptor(this, 'obj1'); // obj1PropertyDesc { // configurable: false // enumerable: true // value: Object // writable: true // }
There are a couple of notable internal properties that are found on all ES3 and ES5 objects.
[[Get
]] and [[Put
]].
The Ecma specs enclose internal properties in double square brackets as a convention only.
In ES3 [[Get
]] and [[Put
]] are used to return and set the internal [[Value
]] property.
According to the Ecma5 spec, the internal [[Get
]] and [[Put
]] properties appear to do the same thing, although it’s not stated explicitly.
This may just be an oversight of the spec.
Accessor Properties
All the examples so far have been showing data properties.
By default properties are data properties unless they define a getter and/or setter,
in which case they are defined as accessor properties.
There are two attributes that are distinct to accessor properties.
get
and set
.
Both of which allow a method (and only a method) to be assigned to them to get
or set
respectively.
- Internally the getter calls the functions internal [[
Call
]] method with no arguments. - Internally the setter calls the functions internal [[
Call
]] method with an arguments list containing the assigned value as its sole argument.
The setter may but is not required to have an effect on the value returned by subsequent calls to the properties internal [[Get
]] method.
So these attributes may or may not leverage the internal [[Get
]] and [[Put
]] properties that are found in ES3 and ES5 on all objects.
You can in fact define only a getter (readonly), or only a setter (write-only) accessor if you so choose.
Defining accessor properties literally:
var testObj = { // An ordinary data property dataProp: 'value', // An accessor property defined as a pair of functions // get accessorProp() { return this.dataProp; }, set accessorProp(value) { this.dataProp = value; } }; testObj.accessorProp = 'an updated string'; alert(testObj.accessorProp); // undefined alert(testObj.dataProp); // an updated string
Can we create a data (default) property and then change it to be an accessor property?
var testObj = {}; // Start with no properties at all // Add a nonenumerable data property x with value 1. Object.defineProperty(testObj, 'x', { value : 1, writable: true, enumerable: false, configurable: true}); // Check that the property is there but is non-enumerable alert(testObj.x); // 1 // check that we can't enumerate the testObj alert(Object.keys(testObj)); // returns an empty array of strings // Now modify the property x so that it is read-only Object.defineProperty(testObj, 'x', { writable: false }); // Try to change the value of the property testObj.x = 2; // Fails silently or throws TypeError in strict mode alert(testObj.x); // 1 // The property is still configurable, so we can change its value like this: Object.defineProperty(testObj, 'x', { value: 2 }); alert(testObj.x); // 2 // what happens if we change configurable to false? Object.defineProperty(testObj, 'x', { configurable: false }); Object.defineProperty(testObj, 'x', { value: 2.5 }); // Uncaught TypeError: Cannot redefine property: x // Now change x from a data property to an accessor property // providing we haven't set configurable to false as above. Object.defineProperty(testObj, 'x', { get: function() { return 0; } }); alert(testObj.x); // 0
Yip.
Ok, so what does a property descriptor of an Accessor Property look like?
var objWithMultipleProperties; var objWithMultiplePropertiesDescriptor; objWithMultipleProperties = Object.defineProperties({}, { x: { value: 1, writable: true, enumerable:true, configurable:true }, y: { value: 1, writable: true, enumerable:true, configurable:true }, r: { get: function() { return Math.sqrt(this.x*this.x + this.y*this.y) }, enumerable:true, configurable:true } }); objWithMultiplePropertiesDescriptor = Object.getOwnPropertyDescriptor(objWithMultipleProperties, 'r'); // objWithMultiplePropertiesDescriptor { // configurable: true, // enumerable: true, // get: function () { // // other members in here // }, // set: undefined, // // ... // }
The Global Object
When the JavaScript interpreter starts (or whenever a web browser loads a new page),
it creates a new global object and gives it an initial set of properties that define:
• global properties like undefined
, Infinity
, and NaN
• global functions like isNaN()
, parseInt()
, and eval()
• constructor functions like Date()
, RegExp()
, String()
, Object()
, and Array()
• global objects like Math
and JSON
delete undefined; // not deleted delete Infinity; // not deleted delete NaN; // not deleted delete isNaN; // deleted delete parseInt; // deleted delete eval; // deleted delete Date; // deleted delete RegExp; // deleted delete String; // deleted delete Object; // deleted delete Array; // deleted delete Math; // deleted delete JSON; // deleted undefined = 'kims undefined'; // nonassignable Infinity = 'kims infinity'; // nonassignable NaN = 'kims nan'; // nonassignable
- Why are
undefined
,Infinity
andNaN
not removed? - Are they non-configurable?
- Why are they non-assignable?
- How do we test this?
- Are they constants?
- Are
Infinity
,NaN
andundefined
reserved words?
I’ll answer these questions shortly.
ES3 properties
According to the standard
8.6 “Each property consists of a name, a value and a set of attributes”.
8.6.1 A property can have zero or more attributes from the following set:
These attributes along with others (see ES3 spec) are reserved for internal use.
Attribute | Description |
ReadOnly | The property is a read-only property. Attempts by ECMAScript code to write to the property will be ignored. (Note, however, that in some cases the value of a property with the ReadOnly attribute may change over time because of actions taken by the host environment; therefore “ReadOnly ” does not mean “constant and unchanging”!) |
DontEnum | The property is not to be enumerated by a for-in enumeration |
DontDelete | Attempts to delete the property will be ignored. |
Internal | Internal properties have no name and are not directly accessible via the property accessor operators. This means the property is not accessible to the ECMAScript program. How these properties are accessed is implementation specific. How and when some of these properties are used is specified by the language specification. |
These property attribute values can not be changed
An interesting Internal property is the [[Prototype
]]
There are a number of ways to access the internal [[Prototype
]] property indirectly.
I’ve detailed them in my post on prototypes here.
More on ES5 properties
writable
, enumerable
and configurable
replace the ES3 property attributes: ReadOnly
, DontEnum
, DontDelete
.
The property attributes and their values define the property descriptor object (including Data or Accessor properties and those that apply to both (enumerable
and configurable
)).
The property attributes can be manually managed by the:
Object.defineProperty
and Object.defineProperties
methods
Object.getOwnPropertyDescriptor
var myObj = {}; Object.defineProperty(myObj, 'propOnMyObj', { value: 'property descriptor', writable: true, // ReadOnly = false in ES3 enumerable: false, // DontEnum = true in ES3 configurable: true // DontDelete = false in ES3 }); console.log(myObj.propOnMyObj); // 'property descriptor' // getOwnPropertyDescriptor is the only way to get the properties attributes. // They don't exist as visible properties on the property (other than for setting them as above), // they're stored internally in the ECMAScript engine. var myPropertyDescriptor = Object.getOwnPropertyDescriptor(myObj, 'propOnMyObj'); console.log(myPropertyDescriptor.enumerable); // false console.log(myPropertyDescriptor.writable); // true // etc.
There’s lots of new methods defined in ES5.
Now, back to the six questions we had above.
- Why are
undefined
,Infinity
andNaN
not removed?
Because their property descriptorsconfigurable
attribute is set tofalse
. - Are they non-configurable?
Yes, as above. - Why are they non-assignable?
Because their property descriptorswritable
attribute is set tofalse
. - How do we test this?
- Are they constants?
Effectively, yes. - Are
Infinity
,NaN
andundefined
reserved words?
No. Avoid using their names to remove ambiguity.
ES3
Infinity read/write (the value can be changed). Holds positive infinity.
—Number is a property on the global object, which has readonly properties Infinity
and NaN
.
NaN read
/write
(the value can be changed).
undefined
ES5
Infinity
(well… POSITIVE_INFINITY
) is a property on the global Number
property with the value Infinity
—We now also have NEGATIVE_INFINITY
with the value –Infinity
—Infinity
is also declared directly on the global object.
NaN
is a property on the global object with the value NaN
undefined
is a property on the global object with the value (you guessed it) undefined
.
These are all constants now.
Important differences between Properties and Variables
Variables are properties, but not vice versa.
The VariableObject in ES3 is called the VariableEnvironment in ES5.
Can be seen in the specs.
Not sure why they changed what they called it.
Each execution context (be it global or any function) has an associated VariableObject.
Variables (and functions) created within a given context are bound as properties of that context’s VariableObject.
Even function parameters are added as properties of the VariableObject.
Discussed in depth in:
ECMAScript3 spec under “10 Execution Contexts”
ECMAScript5 spec under “10.3 Execution Contexts” onwards
This is why we can access global variables as properties of the global object…
Because that’s what they are.
- The global object is created before control enters any execution context.
- The global object is the same as the global contexts VariableObject.
- In the
HTML DOM
; thewindow
property of the global object is the global object.
Now variables of functions are similar, but we can’t access them as properties.
Why?…
ECMAScript has an Activation Object.
When control enters the execution context of a function, an activation object is created and associated with the execution context.
The activation object is initialised with:
- The
this
value - an
arguments
property (referred to as a binding in ES5 spec) that has theDontDelete
attribute (configurable
set tofalse
in ES5).
The activation object is then used as the VariableObject.
We can access members of the activation object but not the activation object itself,
which is why we can’t access the members as properties.
Further details in:
ECMAScript3 spec under “10.2 Entering An Execution Context”
ECMAScript5 spec under “10.4 Establishing an Execution Context”
Feature Detection (Yes, Including JavaScript)
I know this is not property specific, but it was something I thought noteworthy.
There’s a library that looks to have potential for JavaScript feature detection.
“has.js”
This should be useful for detecting what your users browsers are capable of EcmaScript wise.
The project lead is Peter Higgins (Dojo Toolkit project lead).
Has a good sized group of committers.
May have potential to be a better Modernizr.
The source is here.
Explanation of has.js features here.
Additional References:
Succinct explanation of Variables vs Properties in JavaScript
EcmaScript5 Objects and Properties
Slideshow by Doug Crockford on ES5’s new parts
Dmitry Soshnikov’s elaborations on the Ecma standards:
http://dmitrysoshnikov.com/ecmascript/es5-chapter-1-properties-and-property-descriptors/
http://dmitrysoshnikov.com/ecmascript/chapter-7-2-oop-ecmascript-implementation/
http://dmitrysoshnikov.com/ecmascript/chapter-2-variable-object/