JavaScript definite guide – Objects

JavaScript's fundamental datatype is the object. An object is a composite value: it aggregates multiple values (primitive values or other objects) and allows you to store and retrieve those values by name. An object is an unordered collection of properties, each of which has a name and a value.

Any value in JavaScript that is not a string, a number, true, false, null or undefined is an object. And even though strings, numbers, and booleans. Are not objects, they behave like immutable objects.

A property has a name and a value. A property name may be any string, including the empty string, but no object may have two properties with the same name. The value may be any JavaScript value, or it may be a getter or a setter function (or both). In addition to tis name and value, each property has associated values that we'll call property attributes:

  • The writable attribute specifics whether the value of the property can be set.
  • The enumerable attribute specifies whether the property name is returned by a for/in loop.
  • The configurable attribute specifies whether the property can be deleted and whether its attributes can be altered.

Prior to ECMAScript 5, all properties in objects created by your code are writable, enumerable, and configurable. In ECMAScript 5, you can configure the attributes of your properties. 6.7 explains how to do this:

In addition to its properties, every object has three associated object attributes:

  • An object's prototype is a reference to another object from which properties are inherited.
  • An object's class is a string that categorizes the type of an object.
  • An object's extensible flag specifies whether new properties may be added to the object.

Finally, here are some terms we'll use to distinguish among three broad categories of JavaScript objects and two types of properties:

  • A native object is an object or class of objects defined by the ECMAScript specification. Arrays, functions, dates, and regular expressions (for example) are native objects.

  • A host object is an object defined by the host environment (such as a web browser) within which the JavaScript interpreter is embedded.
    • The HTMLElement objects that represent the structure of a web page in client-side JavaScript are host objects.

    Host objects may also be native objects, as when the host environment defines methods that are normal JavaScript function objects.

  • A user-defined: is an object or class objects defined directly on an object.

  • An own property is property defined directly on an object.

  • An inherited property is property defined by an object's prototype object.

Creating Objects

Objects can created with object literals, with the new keyword, and with the Object.create() function. The subsections below describe each technique.

Object literals

The easiest way to create an object is to include an object literal in your JavaScript code. An object literal is a comma-separated list of colon-separated name:value pars, enclosed within curly braces. A property name is a JavaScript identifier or a string literal (the empty string is allowed).A property value is any JavaScript expression; the value of the expression becomes the value of the property. Here are some examples:

An object literal is an expression that creates and initializes a new and distinct object each time it is evaluated. The value of each property is evaluated each time the literal is evaluated.

Creating Objects with new

The new operator creates and initializes a new object. The new keyword must be followed by a function invocation. A function used in this was is called a constructor and serves to initialize a newly created object. Core JavaScript includes built-in constructors for native types. For example:

var o = new Object()
var a = new Array()
var d = new Date()
var r = new RegExp("js")

In addition to these built-in constructors, it is common to define your own constructor functions to initialize newly created objects.

Prototypes

Before we can cover the third object creation technique, we must pause for a moment to explain prototypes. Every JavaScript object has a second JavaScript object (or null but this is rare) associated with it. This second object is known as prototype, an the first object inherited properties from the prototype.

All objects created by object literals have the same prototype object, and we can refer to this prototype object in JavaScript code as Object.prototype. Objects created using the new keyword and a constructor invocation use the value of the prototype property of the constructor function as their prototype. So the object created by new Object() inherits from Object.prototype just as the object created by {} does. Similarly, the object created by new Array() uses Array.prototype as its prototype, and the object created by new Date() uses Date.prototype as its prototype.

Object.prototype is one of the rare objects that has no prototype; it does not inherited any properties. Other prototype objects are normal objects that do have a prototype. All of the built-in constructors (and most user-defined constructors) have a prototype that inherits from Object.prototype. For example, Date.prototype inherits properties from Object.prototype, so a Date object created by new Date() inherits properties from both Date.prototype and Object.prototype. This linked series of prototype objects is known as prototype.chain.

As explanation of how property inheritance works in 6.2.2. We'll learn how to query to prototype of an object in 6.8.1. And Chapter 9 explains the connection between prototypes and constructors in more detail: it shows how to define new "classes" of objects by writing constructor function and setting its prototype property to the prototype object to be used by the "instances" created with that constructor.

Object.create()

Object.create() is a static function, not a method invoked or individual objects. To use it, simply pass the desired prototype object:

var o1 = Object.create({x:1, y:2})

You can pass null to create a new object that does not have a prototype, but if you do this, the newly created object will not inherit anything, not even basic methods like toString() (which means it won't work with the + operator either):

var o2 = Object.create(null)

If you want to create an ordinary empty object (like the object returned by {} or new Object()), pass Object.prototype:

var o3 = Object.create(Object.prototype)    // o3 is like {} or new Object()

The ability to create a new object with an arbitrary prototype (put another way: the ability to create an "heir" for any object) is a powerful one, and we can simulate it in ECMAScript 3 with a function like the one.

function inherit(p) {
  if (p == null) throw TypeError()  // p must be non-null object
  if (Object.create)        // If Object.create() is defined ...
    return Object.create(p) //  then just use it.
  var t = typeof p          // Otherwise do some more type checking
  if (t !== "object" && t !== "function")
  fucntion f() throw TypeError()    // Define a dummy constructor function.
  f.prototype = p   // Set its prototype property to `p`.
  return new f()    // Use `f()` to create an "heir" of `p`.
}

The code in the inherit() will make more sense after we've covered constructors in Chapter 9. For now, please just accept that it returns a new object that inherits the properties of the argument object.Note that inherit() is not a full replacement for Object.create(): it does not allow the creation of objects with null prototypes, and it does not accept the optional second argument that Object.create() does. Nevertheless, we'll use inherit() in a number of examples in this chapter and gain in Chapter 9.

To understand why this works, you need to know how properties are queried and set in JavaScript. These are the topics of the next section.

Querying and Setting Properties

To obtain the value of a property, use the dot (.) or square bracket ([]) operators described in 4.4. The left-hand should be an expression whose value is an object. If using the dot operator, the right-hand side should be an expression whose value is an object. If using square brackets, the value within the brackets must be an expression that evaluates to a string that contains the desired property name:

var author = book.author
var name = author.name
var title = book["main tile"]

IN ECMAScript 3, the identifier that follows the dot operator cannot be reserved word: you cannot write o.for or o.class, for example, because for is a language keyword and class is reserved for future use. If an object has properties whose name is a reserved word, you must use square bracket notation to access them: o["for"] and o["class"]. ECMAScript 5 relaxes this restriction (as do some implementations of ECMAScript 3) and allows reserved words to follow the dot.

When using square bracket notation, we've said that the expression inside the square brackets must evaluate to a string. A more precise statement is that the expression must evaluate to a string or a value that can be converted to a string.

Objects As Associative Arrays

As explained above, the following two JavaScript expressions have the same value:

object.property
object["property"]
  • The first syntax, using the dot and an identifier, is like the syntax to access a static filed or a struct or object in C or Java.
  • The second syntax, using square brackets and a string, looks like array access, but to an array indexed by strings rather than by numbers. This kind of array is known as an associative array (or hash or map or dictionary).

JavaScript objects are associative arrays, and this section explains why that is important.

In C, C++, Java, and similar strongly typed languages, an object can have only a fixed number of properties, and the names of these properties must be defined in advance. Since JavaScript is a loosely typed language, this rule does not apply: a program can create any number of properties in any object. When you use the . Operator to access a property of an object, however, the name of step property is expressed as an identifier. Identifiers must be typed literally into your JavaScript program; they are not a datatype, so they cannot be manipulated by the program.

On the other hand, when you access a property of an object with the [] array notation, the name of the property is expressed as a string. Strings are JavaScript datatypes, so that they can be manipulated and created while a program is running. So, for example, you can write the following code in JavaScript:

var addr = ""
for (i = 0; i < 4; i++) {
  addr += customer["address" + i] + '\n'
}

This code reads and concatenates the address0, address1, address2, and address 3 properties of the customer object.

This brief example demonstrates the flexibly of using array notation to access properties of an object with string expressions. The code above could be rewritten using the dot notation, but there are cases in which only the array notation will do. Suppose, for example, that you are writing a program that uses network resources to compute the current value of the user's Stockmarket investments. The program allows the suer to type in the name of each stock. You might use an object named portfolio to hold this information. The object has one property of each stock. The name of the property is the name of the stock, and the property is the number of shares of that stock. So for example, if a user holds 40 shares of stock in IBM, the portfolio.ibm property has the value 50.

Part of this program might be a function for adding a new stock to the portfolio:

function addstock(portfolio, stockname, shares) {
  portfolio[sotckname] = shares
}

Since the user enters stock names at runtime, there is no way that you can know that property names ahead of time. Since you can't know the property names when you write the program, there is no way you can use the . operator to access the properties of the portfolio object. You can use the [] operator, however, because it uses a string value (which is dynamic and can change at runtime) rather than an identifier (which is static and must be hardcoded in the program) to the name property.

Here's how you'd use it the computing the total value of a portfolio:

function getvalue(portfolio) {
  var total = 0.0
  for (stock in portfolio) {
    var shares = portfolio[stock]
    var price = getquote(stock)
    total += shares * price
  }
  return total
}

Inheritance

JavaScript objects have a set of "own properties," and they also inherit a set of properties from the prototype object. To understand this, we must consider property access in more detail.

Suppose you query the property x in the object o.

  • If o doesn't have an own property with that name, the prototype object of o is queried for the property x.
  • If the prototype object does not have own property by that name, but has a prototype itself, the query is performed on the prototype by that name, but has a prototype itself, the query is performed on the prototype of the prototype.
  • This continues until the property x is found or until an object with a null prototype is searched.

As you can see, the prototype attribute of an object creates a chain or linked list from which properties are inherited.

var o = {}
o.x = 1
var p = inherit(o)
p.y = 2
var q = inherit(p)
q.z = 3
var s = q.toString()
q.x + q.y

Property assignment examines the prototype chain to determine whether the assignment is allowed. If o inherits a read-only property named x, for example, then the assignment is not allowed. If the assignment is allowed, however, it always creates or sets a property in the original object always creates or set s a property in the original object and never modifies the prototype chain. The fact that inheritance occurs when querying properties but not when setting them is a key feature of JavaScript because it allow us to selectively override inherited properties:

var unitcircle = { r:1 }
var c = inherit(unitcircle)
c.x = 1; c.y = 1
c.r = 2
unitcircle.r

There is one exception to the rule that a property assignment either fails or creates or sets a property in the original object. If o inherits the property x, and that property is an accessory property with a setter method, then that setter method is called rather than creating a new property x in o. Note, however, that the setter method is called on the object o, not on the prototype object that defines the property, so if the setter method defines any properties, ti will do so on o, and it will agin leave the prototype chain unmodified.

Property Access Errrors

It is not an error to query a property that does not exist. If the property x is not found as an own property or an inherited property of o, the property access expression o.x evaluates to undefined.

book.subtitle

It is an error, however, to attempt to query a property of an object that does not exist. The null and undefined values have no properties, and it is an error to query properties of these values. Continue the above example:

var len = book.subtitle.length

Unless you are certain that both book and book.subtitle are (or behave like) objects, you shouldn't write the expression book.subtitle.length, since it might raise an exception. Here are two ways to guard against this kind of exception:

// A verbose and explicit technique
var len = undefined
if (book) {
  if (book.subtitle) len = book.subtitle.length
}

// A concise and idiomatic alternative to get subtitle length or undefined 
var len = book && book.subtitle && book.subtitle.length

Attempting to set a property on null or undefined also causes a TypeError, of course. Attempts to set properties on other values do not always succeed, either: some properties are read-only and cannot be set, and some objects do not allow the addition of new properties. Curiously, however, these failed attempts to set properties usually fail silently:

// The prototype properties of built-in constructors are ready-only.
Objecet.prototype = 0   // Assignment fials silently; Object.prototype unchanged.

This historical quirk of JavaScript is rectified in the strict mode of ECMAScript 5. In strict mode, any failed attempt to set a property throws a TypeError exception.

The rules that specify when a property assignment succeeds and when it fails are intuitive but difficult to express concisely. An attempt to set a property p of an object o fails in these circumstances:

  • o has an own property p that is read-only: it is not possible to set read-only properties.
  • o has an inherited property p that is read-only:
  • o does not have an own property p; o does not inherit a property p with a setter method, and o's extensible attribute is false. If p does not already exist on o, and if there is no setter method to call, then p must be added to o. But if o is not extensible, then no new properties can be defined on it.

Deleting Properties

The delete operator removes a property from an object. Its single operand should be a property access expression. Surprisingly, delete doesn't operate on the value of the property but on the property itself:

delete book.author
delete book["main title"]

The delete operator only deletes own properties, not inherited ones. (To delete an inherited property, you must delete it from the prototype object in which it is defined. Doing this affects every object that inherits from that prototype.)

A delete expression evaluates to true if the delete succeeded or if the delete had no effect (such as deleting a nonexistent property). delete also evaluates to true when used (meaninglessly) with an expression that is not a property access expression:

o = {x:1}
delete o.x
delete o.x
delete o.toString
delete 1

delete does not remove properties that have a configurable attribute of false. (Though it will remove configurable properties of nonextensible objects.) Certain properties of built-in objects are noncofigurable, and properties of the global created by variable declaration and function declaration. In strict mode, attempting to delete a non configurable property causes a TypeError. In non-strict mode (and in ECMAScript 3), delete simply evaluates to false in this case:

delete Object.prototype
var x = 1
delete this.x
function f() {}
delete this.f

When deleting configurable properties of the global object in non-strict mode, you can omit this reference to the global object and simply follow the delete operator with the property name:

this.x = 1
delete x

In strict mode, however, delete raises a SyntaxError if its operand is an unqualified identifier like x, and you have to be explicit about the property access:

delete x    // SyntaxError in strict mode
delete this.x   // This works

Testing properties

JavaScript objects can be taught as sets of properties, and it is often useful to be able to test for membership in the set — to check whether an object has a property with a given name. You can do this with the In operator, with the hasOwnProperty() and propertyIsEnumerable() methods, or simply by queuing the property.

The in operator expects a property name (as a string) on its left side and object on its right. It returns true if the object has an own property or an inherited property by that name:

var o = { x: 1 }
"x" in o
"y" in o
"toString" in o

The hasOwnProperty() method of an object tests whether that object has an own property with the given name. It returns false for inherited properties:

var o = { x: 1 }
o.hasOwnProperty("x")
o.hasOwnProperty("y")
o.hasOwnProperty("toString")

The propertyIsEnumerable() refines the hasOwnProperty() test. It returns true only if the named property is an own property and its enumarble attribute is true. Certain built-in properties are not enumerable. Properties created by normal JavaScript code are enumerable unless you've used one of the ECMAScript 5 methods shown later to make them nonenumerable.

var o = inherit({ y:2 })
o.x = 1
o.propertyIsEnumerable("x") // true:
o.propertyIsEnumerable("y") // false:
Object.prototype.propertyIsEnumerable("toString")   // false:

Instead of using the in operator it is often sufficient to simply query the property and use !== to make sure it is not undefined:

var o = { x: 1 }
o.x !== undefined
o.y !== undefined
o.toString !== undefined

There is one thing the in operator can do that the simple property access technique show above cannot do. in can distinguish between properties that do not exist and properties that exit but have been set to undefined. Consider this code:

var o = { x: undefined }
o.x !== undefined   // false
o.y !== undefined   // false
"x" in o    // true
"y" in o    // flase
delete o.x
"x" in o    //false

Note that the code above use the !== operator instead of !=. !== and === distinguish between undefined and null. Sometimes, however, you don't want to make this distinction:

// If `o` has a property `x` whose value is not null or undefined, double it.
if (o.x != null) o.x *= 2

// If o has a property x whose value does not convert to false, double it.
// If x is undefined, null, false, "", 0, or NaN, leave it alone.
if (o.x) o.x *= 2

Enumerating Properties

Instead of testing for the existence of individual properties, we sometimes want to iterate through or obtain a list of all the properties of an object. This is usually done with the for/in loop, although ECMAScript 5 provides two handy alternatives.

The for/in loop was covered in 5.5.4. It runs the body of the loop once for each the property (own or inherited) or the specified object, assigning the name of the property to the loop variable. Built-in methods that objects inherit are note enumerable, but the properties that your code adds to objects are enumerable (unless you use one of the functions described later to make them nonenumerable). For example:

var o = {x:1, y:2, z:3}
o.propertyIsEnumerable("toString")
for (p in o)
  console.log(p)

Some utility libraries add new methods (or other properties) to Object.prototype so that they are inherited by, and available to, all objects.

for (p in o) {
  if (!o.hasOwnProperty(p)) continue    // Skip inherited properties
}

for (p in o) {
  if (typeof o[p] === "function") continue
}

Example 6.2 defends utility functions that use for/in loops to manipulate object properties in helpful ways. The extend() function, in particular, is one that is commonly included in JavaScript utility libraries.

/*
* Copy the enumerate properties of p to o, and return o.

*/

function extend(o, p) {
  for (prop in p) {
    o[prop] = p[prop]
  }
  return o
}

/*
 * Copy the enumerable properties of p to o, and return o.
 * If o and p have a property by the same name, o's propety is left alone. This fucntion does not handle getters and setters or copy attributes.
*/
function merge(o, p) {
    for (prop in p) {
        if (o.hasOwnProperty[prop]) continue
        o[prop] = p[prop]
    }
    return o
}

/*
 * Remove properties from o if there is not a property with the smae name in p.
 * Return o.
*/
function restrict(o, p) {
  for (prop in o) {
    if (!(prop in p)) delete o[prop]
  }
  return o
}

/*
 * For each property of p, delete the property with the same name from o.
 * Return o. 
*/
function subtract(o, p) {
  for (prop in p) {
    delete o[prop]
  }
  return o
}

/*
 * Return a new object that holds the properties of both o and p.
 * If o and p have properties by the same name, the values from o are used.
*/
function union(o, p) { return extend(extend({}, o), p) }

/*
 * Return a new object that holds only the properteis of o that also appear in p. This is something link the interesection of o and p, but the value of the properties in p are discarded.
*/
function interesection(o, p) { return restrick(extend({}, o), p) }

/*
 * Return an array that holds the names of the enumerable own properties of o.
*/
function keys(o) {
  if (typeof o !== "object") throw TypeError()
  var result = []
  for (var prop in o) {
    if (o.hasOwnProperty(prop))
        result.push(prop)
  }
  return result
}

In addition to the for/in loop, ECMAScript 5 denies two functions that enumerate property names. The first is Object.getOwnPropertyNames(). It works like Object.keys() but returns the names of all the own properties of the specified object, t not just the enumerable properties. There is no way to write this function in ECMAScript 3, because ECMAScript 3 does not provide a way to obtain the none numerable properties of an object.

Property Getters and Setters

We've said that an object property is a name, a value, and a set of attributes. In ECMAScript 5 the value may be replaced y one or two methods, known as a getter and a setter. Properties defined by getters and setters are sometimes known as accessor properties to distinguish them from data properties that have a simple value.

When a program queries the value of an accessor property, JavaScript invokes the getter method (passing no arguments). The return value of this method becomes the value of the property access expression. When a program sets the value of an accessor property, JavaScript invokes the setter method, passing the value of the right-hand side of the assignment. This method is responsible for "setting," in some sense, the property value. The return value of the setter method is ignored.

Accessor properties do not have a writable attribute as data properties do. If a property has both a getter and a setter method, it is a read/write property. If it has only a getter method, it is a read-only property. And if it has only a setter method, it is a write-only property (something that is not possible with data properties) and attempts to read it always evaluate to undefined.

The easiest way to define accessor properties is with an extension to the object literal syntax:

var o = {
  // An ordinary data property
   data_prop: value,
  
  // An accessor property defined as a pair of functions
  get accessor_prop() { /* funciton body here */ }
  set accessor_prop(vale) { /* function body here */ }
}

Accessor properties are defined as one or two functions whose name is the same as the property name, and with the function keyword replaced with get and/or set. Note that no colon I used to separate the ahem of the property from the functions the access that property, but that a comma is still required after the function body to separate the method from the next method or data property.

As an example, consider the following object the represents a 2D Cartesian point. It has ordinary dat properties to represent the X and Y coordinates of the points, and it has accessor properties for the equivalent polar coordinates of the point:

var p = {
  // x and y regular read-write data properties,
  x: 1.0,
  y: 1.0,
  
  // r is a read-write accessor property with getter and setter.
  get r() { return Math.sqrt(this.x * this.x + this.y * this.y) },
  set r(newvalue) {
    var oldvalue = Math.sqrt(this.x * this.x + this.y * this.y)
    var ratio = newvalue/oldvalue
    this.x *= ratio
    this.y *= ratio
  },
  
  // theta is a ready-only accessor property with getter only.
  get theta() { return Math.atan2(this.y, this.x) }
}

Note the use of the keyword this in the getters and setter above. JavaScript invokes these functions as methods of the object on which they are defined, which means that within the body of the function this refers to the point object. So the getter method for the r property can refer to the x and y properties as this.x and this.y. Methods and the this keyword are covered in more details in 8.2.2.

Accessor properties are inherited, just as data properties are, so you can use the object p defined above as a prototype for other points. You can give the new objects their own x and y properties, and they'll inherit the r and theta properties:

var q = inherit(p)
q.x = 0, q.y = 0
console.log(q.r)
console.log(q.theta)

The code above use a accessor properties to define an API the provides two representations (Cartesian coordinates and polar coordinates) of a single set of data. Other reasons to use accessor properties include sanity checking of property writes and returning different values of each property read:

// This object generates strictly increasing serial numbers
var serialnum = {
  // This data property holds the next serial number.
  // The $ in the property name hints that it is a private property.
  $n: 0,
  
  // Return the current value increment it
  get next() { return this.$n++ },
  
  // Set a new value of n, but only if it is larger than current
  set next(n) {
    if (n >= this.$n) this.$n = n
    else throw "serial number can only be set to a larger valu"
  }
}

Finally, here is one more example the uses a getter method to implement a property with "magical" behavior.

// This object has accessor properties that return random numbers.
// This expression "random.octet", for example, yields a random number between 0 and 255 each tiem it is evaluated.
var random = {
  get octet() { reutrn Math.floor(Math.random() * 256) },
}

This section has shown only how to define accessor properties when creating a new object from object literal. The next section shows how to add accessor properties to existing objects.


Property Atributes

In addition to a name and value, properties have attributes that specify whether they can be written, enumerated, and configured. In ECMAScript 3, there is no way to set these atributes: all properties created by ECMAScript 3 programs are writable, enumerable, and configurable, and there is no way to change this. This section explains the ECMAScript 5 API for querying and setting property attributes. This API is particuarly important to library authors because:

  • It allows them to add methods to prototype objects and make them nonenumerable, like built-in methods,
  • It allows them to "lock down" their objects, defining properties that cannot be changed or deleted.

For the purposes of this section, we are going to consider getter and setter methods of an accessor property to be property attributes. The four attributes of a data property are value, writable, enumerable, and configurable. Accessor properties don't have a value attribute or a writable attribute: their writability is determined by the presence or absence of a setter. So the four attributes an accessor property are get, set, enumerable, and configurable.

The ECMAScript 5 methods for querying and setting the attributes of property use an object called a property descriptor to represent the four attributes. A propertty descriptor object has properties with the same names as the attributes of the property it describes. Thus, the property descriptor object of data property has properties named value, writable, enumerable_, and configurable. And the descriptor for an access property has get and set properties instead of value and writable. The writable, enumerable, and configurable properties are boolean values, and the get and set properties are function values, of course.

To obtain the propertty descriptor for a named property of a specified object, call Object.getOwnPropertyDescriptor():

// Returns {value: 1, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor({x:1}, "x")

// Now query the octet property of the random object defined above.
// Returns { get: /*func*/, set:undefined, enumerable:true, configurable:true}
Object.getOwnPropertyDescriptor(random, "octet")

// Returns undefined for inherited properties and properteis that don't exist.

As its name implies, Object.getOwnPropertyDescriptor() works only for own properties. To query the attributes of inherited properties, you must explitly traverse the prototype chain.

To set the attributes of a property, or to create a new property with specified attributes, call Object.defineProperty(), passing the object to be modified, the name of the property to be created or altered, and the property descriptor object:

var o = {}
// Add a nonenumerable data property x with value 1.
Object.defineProperty(o, "x", { value: 1,
writable: true,
enumerable: false,
configurable: true})

// Check that the property is there but is nonenumerable
o.x     // => 1
Object.keys(o)  // => []

// Now modify the property x so that it is  read-only
Object.defineproperty(o, "x", { writable: false })

// Try to change the value of the property
o.x = 2
o.x

// The property is still configurable, so we can change its value like this:
Object.defineProperty(o, "x", { value: 2 })
o.x     // => 2

// Now change x from a data property to an accessor property
Object.defineProperty(o, "x", { get: function() { return o }})
o.x     // => 0

The property descriptor you pass to Object.defineProperty() does not have to include all four attributes. If you're creating a new property, then omitted attributes are taken to be false or undefined. If you're modifying an existing property, then the attributes you omit are simply left unchanged. Note that this method alters an existing own property or creates a new own property, but it will not alter an inherited property.

If you want to create or modify more than one property at a time, use Object.defineProperties(). The first argument is the object that is to be modified. The second argument is an object that maps the names of the properties to be created or modified to the property descriptors for those properties. For example:

var p = 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
  }
})

This code starts with an empty object, then adds two data properties and one read-only accessor property to it. It relies on the fact that Object.defineProperties() returns the modified object.

We saw the ECMAScript 5 method Object.create() in 6.1. We learned there that the first argument to that method is the prototype for the newly created object. This method also accepts a second optional argument, which is the same as the second argument to Object.defineProperties(). If you pass a set of property descriptors to Object.create(), then they are used to add properties to the newly created object.

Object.defineProperty() and Object.defineProperties() throw TypeError if the attempt to create or modify a property is not allowed. This happens if you attempt to add a new property to a nonextensible object. The other reasons that these method might throw TypeError have to do with the attributes themselves.

  • The writeable attribute governs attempts to change the value attribute.
  • And the configurable attribute governs attempts to change the other attributes (and also specifies whether a property can be deleted).

The rules are not completely straightforward, however.

  • It is possible to change the value of a nonwritable property if the property is configurable, for example.
  • Also, it is possible to change a property from writable to nonwritable even if that property is non configurable.

Here are the complete rules. Calls to Object.defineProperty() or Object.defineProperties() that attempt to violate them throw TypeError:

  • If an object is not extensible, you can edit its existing own properties, but you cannot add new properties to it.
  • If a property is not configurable, you cannot change its configurable or enumerable attributes.
  • If an accessor property is not configurable, you cannot change its getter or setter method, and you cannot change it to a data property.
  • If a data property is not configurable, you cannot change it to an accessor property.
  • If a data property is not configurable, you cannot change its writable attribute from false to true, but you can change it from true to false.
  • If a data property is not configurable and writable, you cannot change its value.
  • If a data property is not configurable and not writable, you cannot change its value. You can change the value of property that is configurable but nonwritable, however. (because that would be the same as making it writable, then changing the value, then converting it back to nonwritable).

Example 6-2 included an extend() function that copied properties from one object to another. That function simply copied the name and value of the properties and ignored their attributes. Further more, it did not copy the getter and setter methods of accessor properties, but imply converted them into static data properties.

Example 6-3 shows a new version of extentd() that uses Object.getOwnPropertyDescriptor() and Object.defineProperty() to copy all property attributes. Rather than being written as a function, this version is defined as a new Object method and is added as a none numerable property to Object.prototype.

// Example 6-3. Copying property attributes
/*
 * Add a nonenumerable `extend()` method to Object.prototype
 */
Object.definePropety(Object.prototype,
    "extend",
    {
      writable: true,
      enumerable: false,
      configurable: true,
      value: function(o) {
        // Get all own props, even nonenumerable ones
        var names = Object.getOwnPropertyNames(o)
        // Loop through them
        for (var i = 0; i < names.length; i++) {
          // Skip props alterady in the object
          if (names[i] in this)
            continue
          // Get property description from o
          var desc = Objec.tgetOwnPropertyDescriptor(o, names[i])
          // Use it to create property on this
          Object.defineProperty(this, names[i], desc)
        }
      }
    }

Legacy API for Getters and Setters

The object literal syntax for accessor properties described in 6.6 allows us to define accessor properties in new objects, but it doesn't allow us to query the getter and setter methods or to add new accessor properties to existing objects. In ECMAScript 5 we can use Object.getOwnPropertyDescriptor() and Object.defineProperty() to do this things.

Most JavaScript implementations (with the major exception of the IE web browser) supported the object literal get and set syntax even before the adoption of ECMAScript 5. These implementations support a nonstandard legacy API for querying and setting getters and setters. This API consists of four methods available on all objects. And __defineGetter__() and __defineSetter__() define a getter or setter: pass the property name first and the getter or setter method second. The names of each of these methods begin and end with double underscores to indicate that they are nonstandard methods. These nonstandard methods are not documented in the reference section.

Object Attributes

Every object has associated prototype, and extensible attributes. The subsections taht follow explain what these attributes do and(where possible) how to query and set them.

The prototype Attribute

An object's prototype attribute specifies the object from which it inherits properties. This is such an important attribute that we'll usually simply say "the prototype of o" rather than "the prototype attribute of o". Also, it is important to understand that when prototype appears in code font, it refers to an ordinary object property, not to the prototype attribute.

The prototype attribute is set when an object is created. Recall from 6.1.3 that objects created from object literals use Object.prototype as their prototype. And objects created with Object.create() use the first argument to that function (which may be null) as their prototype.

In ECMAScript 5, you can query the prototype of any object by passing that object to Object.getPrototypeOf(). There is no equivalent function in ECMAScript 3, but it is often possible to determine the prototype of an object o using the expression o.constructor.prototype. Objects created with a new expression usually inherit a constructor property that refers to the constructor function used to create the object. And, as described above, constructor functions have a prototype property that specifies the prototype for objects created using that constructor.

To determine whether one object is the prototype of (or is part of the prototype chain of) another object, use the isPrototypeOf() method. To find out if p is the prototype of o write p.isPrototypeOf(o). For example:

var p = {x:1}
var o = Object.create(p)
p.isPrototypeOf(o)
Object.prototype.isPrototype(o)

Note that isPrototypeOf() performs a function similar to the instanceof operator.

Mozilla's implementation of JavaScript (since the early days of Netscape) exposed the prototype attribute through the specially named __prototype__ property, and you can use the property to do directly query or set the prototype of object. Using __proto__ is not portable; it has not been implemented by IE or Opera, although it is currently supported by Safari and Chrome. Versions of Firefox that implement ECMAScript 5 still support __proto__, but restrict is ability to change the prototype of nonextensible objects.

The class Attribute

An object's class attribute is a string that provides information about the type of the object. Neither ECMAScript 3 nor ECMAScript 5 provide any way to test this attribute, and there is only an indirect technique for querying it. The default toString() method returns a string the form:

[object `class`]

So to obtain the class of an object, you can invoke the toString() method on it, and extract the eighth through the second-to-last characters of the returned string. The tricky part is that many objects inherit other, more useful toString() methods, and to invoke the correct version of toString(), we must do so indirectly, using the Function.call() method.

// Example 6-4. A classof() function
function classof(o) {
  if (o === null) return "Null"
  if (o === undefined) return "Undefined"
  return Object.prototype.toString.call(o).slice(8, -1)
}

This classof() function works for any JavaScript value. Numbers, strings, and booleans behave like objects when the topString() method is invoked on them, and the function includes special cases for null and undefined. Objects created through built-in constructors such as Array and Date have class attributes that match the names or their constructors. Host objects typically have meaningful class attributes as well, though this is implementation-dependent. Objects created through object literals or by Object.create have a class attribute of "Object". If you define your own constructor function, any objects you create with it will have a class attribute of "Object": there is not way to specify the class attribute for your own classes of objects:

classof(null)
classof(1)
classof("")
classof(false)
classof({})
classof([])
classof(/./)    // => "Regexp"
classof(new Date())
classof(window)
function f() {} // Define a custom constructor
classof(new f())

The extensible Attribute

The extensible attribute of an object specifies whether new properties can be added to the object or not. In ECMAScript 3, all built-in and user-defined objects are implicitly extensible, and the extensibility of host objects is implementation defined. In ECMAScript 5, all built-in and user-defined objects are extensible unless they have been converted to be nonextensible, and the extensibility of host objects is again implementation defined.

ECMAScript 5 defines functions for querying and setting extensibility of an object. To determine whether an object is extensible, pass it to Object.isExtensible(). In ECMAScript 5, all built-in and user-defined objects is extensible, pass it to Object.isExtensible(). To make an object nonextensible, pass it to Object.preventExtensions().

  • Note that there is no way to make an object extensible again once you have made it nonextensible.
  • Also note the calling preventExtensions() only affects the extensibility of the object itself. If new properties are added to the prototype of a nonextensible object, the nonextensible object will inherit those new properties.

The purpose of the extensible attribute is to be able to "lock down" objects into a known state and prevent outside tampering. The extensible object attribute is often used in conjunction with the configurable and writable property attributes, and ECMAScript 5 defines functions that make it easy to set these attributes together.

Object.seal() works like Object.preventExtensions(), but in addition to making the object nonextensible, it also make all of the own properties of that object non-configurable. This means that new properties cannot be added to the object, and existing properties cannot be deleted or configured. Existing properties that are writable can still be set, however. Existing properties that are writable can still be set, however. There is no way to unseal a sealed object. You can use Object.isSealed() to determine whether an object is sealed.

Object.freeze() locks objects down even more tightly. In addition to making the object nonextensible and its properties non-configurable, it also makes all of the objects own data properties read-only.

It is important to understand that Object.seal() and Object.freeze() affect only the object they are passed: they have no effect on the prototype of that object. If you want to thoroughly lock down an object, you probably need to seal to or freeze the objects in the prototype chain as well.

Object.preventExtensions(),Object.seal(), and Object.freeze() all return the object that they are passed, which means that you can use them in nested function invocations:

// Create a sealed object with a frozen prototype and a noneumerable property
var o = Object.seal(Object.create(Object.freeze({x:1}),
                          {y: {value: 2, writable: true}}))

Serializing Objects

Object serialization is the process of converting an object's state to a string from which it can later be restored. ECMAScript 5 provides native functions JSON.stringify() and JSON.parse() to serialize and restore JavaScript objects. These functions use the JSON data interchange format. JSON stands for "JavaScript Object Notation," and its syntax is very similar to that of JavaScript object and array literals:

o = {x:1 y:{z:[false, numm, ""]}}
p = JSON.stringify(o)
p = JSON.parse(s)

The native implementation of these functions in ECMAScript 5 was modeled very closely after the public-domain ECMAScript 3 implementation available at http://json.org/json2.js. For partial purposes, the implementations are the same, and you can sue these ECMAScript 5 functions in ECMAScript 3 with json2.js module.

JSON syntax is a subset of JavaScript syntax, and it cannot represent all JavaScript values.

  • Objects, arrays, strings, finite numbers, true, false, and null are supported and can be serialized and restored.
  • NaN, Infinity, and -Infinity are serialized to null.
  • Date objects are serialized to ISO-formatted date strings (see the Date.toJSON() function), but JSON.parse() leaves these in string form and does not restore the original Date object.
  • Function, RegExp, and Error objects and the undefiend value cannot be serialized or restored.
  • JSON.stringify() serializes only the enumerable own properties of an object.

If a property value cannot be serialized, the prototype is imply omitted from the stringified output.

Both JSON.stringify() and JSON.parse() accept optional second arguments that can be used to customize the serialization and/or restoration process by specifying a list of properties to be serialized, for example, or by converting certain values during the serialization or stringification process.

Object Methods

As discussed earlier, all JavaScript objects (except those explicitly created without a prototype) inherit properties from Object.prototype. These inherited properties are primarily methods, and because they are universally available, they are of particular interest of JavaScript programmers.

  • We've already seen the hasOwnProperty(), prototype. These inherited properties are primarily methods.

This section explains a handful of universal object methods that are defined on Object.prototype, but which a re intended to be overridden by other, more specialized classes.

The toString() method

The toString() method takes no arguments; it returns a string that somehow represents the value of the object on which it is invoked. JavaScript invokes this method of an object when it needs to convert the object to a string. This occurs, for example, when you use the + operator to concatenate a string with an object or when you pass an object to a method that expects a string.

The default toString() method is not very informative (though it is useful for determining the class of an object). For example, the following line of code simply evaluates to the string "[object Object]":

var s = { x:1, y:1 }.toString()

Because this default method does not display much useful information, many classes define their own versions of toString(). For example, when an array is converted to a string, you obtain a list of the array elements, themselves each converted to a string, and when a function is converted to a string, you obtain the source code for the function. These customized versions of the toString() method are documented in the reference section.

The toLocaleString() Method

In addition to the basic toString() method, objects all have a toLocaleString(). The purpose of this method is to return a localized string representation of the object.

  • The default toLocaleString() method defined by Object doesn't do any localization itself: it simply calls toString() and returns that value.
  • The Date and Number classes define customized versions of toLocaleString() that attempt to format numbers, dates, and times according to local conventions.
  • Array defines a toLocaleString() method that works like toString() except that it formats array elements by calling their toLocaleString() methods instead of toString() methods.

The toJSON() Method

Object.prototype does not actually define a toJSON() method, but the JSON.stringify() method looks for a toJSON() method on any object it is asked to serialize. If this method exists on the object to be serialized, it is invoked, and the return value is serialized, instead of the original object.

The valueOf() Method

The valueOf() method is much like the toString() method, but it is called when JavaScript needs to convert an object to some primitive type other than a string — typically, a number. JavaScript calls this method automatically if an object is used in a context where a primitive value is required. The default valueOf() method does noting interesting, but some of the built-in classes define their own valueOf() .

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.