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,

1
true

,

1
false

,

1
null

or

1
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
    1
    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

1
new

keyword, and with the

1
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

1
new

The

1
new

operator creates and initializes a new object. The

1
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:


1
2
3
4
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

1
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

1
Object.prototype

. Objects created using the

1
new

keyword and a constructor invocation use the value of the

1
prototype

property of the constructor function as their prototype. So the object created by

1
new Object()

inherits from

1
Object.prototype

just as the object created by

1
{}

does. Similarly, the object created by

1
new Array()

uses

1
Array.prototype

as its prototype, and the object created by

1
new Date()

uses

1
Date.prototype

as its prototype.

1
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

1
Object.prototype

. For example,

1
Date.prototype

inherits properties from

1
Object.prototype

, so a Date object created by

1
new Date()

inherits properties from both

1
Date.prototype

and

1
Object.prototype

. This linked series of prototype objects is known as

1
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

1
prototype

property to the prototype object to be used by the "instances" created with that constructor.

1
Object.create()

1
Object.create()

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


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

You can pass

1
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

1
toString()

(which means it won't work with the

1
+

operator either):


1
var o2 = Object.create(null)

If you want to create an ordinary empty object (like the object returned by

1
{}

or

1
new Object()

), pass

1
Object.prototype

:


1
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.


1
2
3
4
5
6
7
8
9
10
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

1
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

1
inherit()

is not a full replacement for

1
Object.create()

: it does not allow the creation of objects with

1
null

prototypes, and it does not accept the optional second argument that

1
Object.create()

does. Nevertheless, we'll use

1
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 (

1
.

) or square bracket

1
([])

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:


1
2
3
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

1
o.for

or

1
o.class

, for example, because

1
for

is a language keyword and

1
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:

1
o["for"]

and

1
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:


1
2
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

1
.

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

1
[]

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:


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

This code reads and concatenates the

1
address0

,

1
address1

,

1
address2

, and

1
address 3

properties of the

1
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

1
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

1
portfolio.ibm

property has the value

1
50

.

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


1
2
3
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

1
.

operator to access the properties of the

1
portfolio

object. You can use the

1
[]

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:


1
2
3
4
5
6
7
8
9
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

1
x

in the object

1
o

.

  • If
    1
    o

    doesn't have an own property with that name, the prototype object of

    1
    o

    is queried for the property

    1
    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
    1
    x

    is found or until an object with a

    1
    null

    prototype is searched.

As you can see, the

1
prototype

attribute of an object creates a chain or linked list from which properties are inherited.


1
2
3
4
5
6
7
8
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

1
o

inherits a read-only property named

1
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:


1
2
3
4
5
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

1
o

inherits the property

1
x

, and that property is an accessory property with a setter method, then that setter method is called rather than creating a new property

1
x

in

1
o

. Note, however, that the setter method is called on the object

1
o

, not on the prototype object that defines the property, so if the setter method defines any properties, ti will do so on

1
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

1
x

is not found as an own property or an inherited property of

1
o

, the property access expression

1
o.x

evaluates to

1
undefined

.


1
book.subtitle

It is an error, however, to attempt to query a property of an object that does not exist. The

1
null

and

1
undefined

values have no properties, and it is an error to query properties of these values. Continue the above example:


1
var len = book.subtitle.length

Unless you are certain that both

1
book

and

1
book.subtitle

are (or behave like) objects, you shouldn't write the expression

1
book.subtitle.length

, since it might raise an exception. Here are two ways to guard against this kind of exception:


1
2
3
4
5
6
7
8
// 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

1
null

or

1
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:


1
2
// 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

1
p

of an object

1
o

fails in these circumstances:

  • 1
    o

    has an own property

    1
    p

    that is read-only: it is not possible to set read-only properties.

  • 1
    o

    has an inherited property

    1
    p

    that is read-only:

  • 1
    o

    does not have an own property

    1
    p

    ;

    1
    o

    does not inherit a property

    1
    p

    with a setter method, and

    1
    o

    's extensible attribute is

    1
    false

    . If

    1
    p

    does not already exist on

    1
    o

    , and if there is no setter method to call, then

    1
    p

    must be added to

    1
    o

    . But if

    1
    o

    is not extensible, then no new properties can be defined on it.

Deleting Properties

The

1
delete

operator removes a property from an object. Its single operand should be a property access expression. Surprisingly,

1
delete

doesn't operate on the value of the property but on the property itself:


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

The

1
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

1
delete

expression evaluates to

1
true

if the delete succeeded or if the delete had no effect (such as deleting a nonexistent property).

1
delete

also evaluates to

1
true

when used (meaninglessly) with an expression that is not a property access expression:


1
2
3
4
5
o = {x:1}
delete o.x
delete o.x
delete o.toString
delete 1
1
delete

does not remove properties that have a configurable attribute of

1
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),

1
delete

simply evaluates to

1
false

in this case:


1
2
3
4
5
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

1
delete

operator with the property name:


1
2
this.x = 1
delete x

In strict mode, however,

1
delete

raises a SyntaxError if its operand is an unqualified identifier like

1
x

, and you have to be explicit about the property access:


1
2
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

1
In

operator, with the

1
hasOwnProperty()

and

1
propertyIsEnumerable()

methods, or simply by queuing the property.

The

1
in

operator expects a property name (as a string) on its left side and object on its right. It returns

1
true

if the object has an own property or an inherited property by that name:


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

The

1
hasOwnProperty()

method of an object tests whether that object has an own property with the given name. It returns

1
false

for inherited properties:


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

The

1
propertyIsEnumerable()

refines the

1
hasOwnProperty()

test. It returns

1
true

only if the named property is an own property and its

1
enumarble

attribute is

1
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.


1
2
3
4
5
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

1
in

operator it is often sufficient to simply query the property and use

1
!==

to make sure it is not undefined:


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

There is one thing the

1
in

operator can do that the simple property access technique show above cannot do.

1
in

can distinguish between properties that do not exist and properties that exit but have been set to

1
undefined

. Consider this code:


1
2
3
4
5
6
7
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

1
!==

operator instead of

1
!=

.

1
!==

and

1
===

distinguish between

1
undefined

and

1
null

. Sometimes, however, you don't want to make this distinction:


1
2
3
4
5
6
// 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

1
for/in

loop, although ECMAScript 5 provides two handy alternatives.

The

1
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:


1
2
3
4
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

1
Object.prototype

so that they are inherited by, and available to, all objects.


1
2
3
4
5
6
7
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

1
for/in

loops to manipulate object properties in helpful ways. The

1
extend()

function, in particular, is one that is commonly included in JavaScript utility libraries.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/*
* 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

1
for/in

loop, ECMAScript 5 denies two functions that enumerate property names. The first is

1
Object.getOwnPropertyNames()

. It works like

1
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

1
undefined

.

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


1
2
3
4
5
6
7
8
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

1
function

keyword replaced with

1
get

and/or

1
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:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

1
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

1
this

refers to the point object. So the getter method for the

1
r

property can refer to the

1
x

and

1
y

properties as

1
this.x

and

1
this.y

. Methods and the

1
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

1
p

defined above as a prototype for other points. You can give the new objects their own

1
x

and

1
y

properties, and they'll inherit the

1
r

and

1
theta

properties:


1
2
3
4
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:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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.


1
2
3
4
5
// 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

1
Object.getOwnPropertyDescriptor()

:


1
2
3
4
5
6
7
8
// 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,

1
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

1
Object.defineProperty()

, passing the object to be modified, the name of the property to be created or altered, and the property descriptor object:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

1
Object.defineProperty()

does not have to include all four attributes. If you're creating a new property, then omitted attributes are taken to be

1
false

or

1
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

1
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:


1
2
3
4
5
6
7
8
9
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

1
Object.defineProperties()

returns the modified object.

We saw the ECMAScript 5 method

1
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

1
Object.defineProperties()

. If you pass a set of property descriptors to

1
Object.create()

, then they are used to add properties to the newly created object.

1
Object.defineProperty()

and

1
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
    1
    writeable

    attribute governs attempts to change the

    1
    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

1
Object.defineProperty()

or

1
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
    1
    false

    to

    1
    true

    , but you can change it from

    1
    true

    to

    1
    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

1
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

1
extentd()

that uses

1
Object.getOwnPropertyDescriptor()

and

1
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

1
Object.prototype

.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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

1
Object.getOwnPropertyDescriptor()

and

1
Object.defineProperty()

to do this things.

Most JavaScript implementations (with the major exception of the IE web browser) supported the object literal

1
get

and

1
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

1
__defineGetter__()

and

1
__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

1
o

" rather than "the prototype attribute of

1
o

". Also, it is important to understand that when

1
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

1
Object.prototype

as their prototype. And objects created with

1
Object.create()

use the first argument to that function (which may be

1
null

) as their prototype.

In ECMAScript 5, you can query the prototype of any object by passing that object to

1
Object.getPrototypeOf()

. There is no equivalent function in ECMAScript 3, but it is often possible to determine the prototype of an object

1
o

using the expression

1
o.constructor.prototype

. Objects created with a

1
new

expression usually inherit a

1
constructor

property that refers to the constructor function used to create the object. And, as described above, constructor functions have a

1
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

1
isPrototypeOf()

method. To find out if

1
p

is the prototype of

1
o

write

1
p.isPrototypeOf(o)

. For example:


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

Note that

1
isPrototypeOf()

performs a function similar to the

1
instanceof

operator.

Mozilla's implementation of JavaScript (since the early days of Netscape) exposed the prototype attribute through the specially named

1
__prototype__

property, and you can use the property to do directly query or set the prototype of object. Using

1
__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

1
__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

1
toString()

method returns a string the form:


1
[object `class`]

So to obtain the class of an object, you can invoke the

1
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

1
toString()

methods, and to invoke the correct version of

1
toString()

, we must do so indirectly, using the

1
Function.call()

method.


1
2
3
4
5
6
// 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

1
classof()

function works for any JavaScript value. Numbers, strings, and booleans behave like objects when the

1
topString()

method is invoked on them, and the function includes special cases for

1
null

and

1
undefined

. Objects created through built-in constructors such as

1
Array

and

1
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

1
Object.create

have a

1
class

attribute of "Object". If you define your own constructor function, any objects you create with it will have a

1
class

attribute of "Object": there is not way to specify the

1
class

attribute for your own classes of objects:


1
2
3
4
5
6
7
8
9
10
11
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

1
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

1
Object.isExtensible()

. In ECMAScript 5, all built-in and user-defined objects is extensible, pass it to

1
Object.isExtensible()

. To make an object nonextensible, pass it to

1
Object.preventExtensions()

.

  • Note that there is no way to make an object extensible again once you have made it nonextensible.
  • Also note the calling
    1
    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.

1
Object.seal()

works like

1
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

1
Object.isSealed()

to determine whether an object is sealed.

1
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

1
Object.seal()

and

1
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.

1
Object.preventExtensions()

,

1
Object.seal()

, and

1
Object.freeze()

all return the object that they are passed, which means that you can use them in nested function invocations:


1
2
3
// 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

1
JSON.stringify()

and

1
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:


1
2
3
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,
    1
    true

    ,

    1
    false

    , and

    1
    null

    are supported and can be serialized and restored.

  • 1
    NaN

    ,

    1
    Infinity

    , and

    1
    -Infinity

    are serialized to

    1
    null

    .

  • Date objects are serialized to ISO-formatted date strings (see the
    1
    Date.toJSON()

    function), but

    1
    JSON.parse()

    leaves these in string form and does not restore the original Date object.

  • Function, RegExp, and Error objects and the
    1
    undefiend

    value cannot be serialized or restored.

  • 1
    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

1
JSON.stringify()

and

1
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

1
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
    1
    hasOwnProperty()

    ,

    1
    prototype

    . These inherited properties are primarily methods.

This section explains a handful of universal object methods that are defined on

1
Object.prototype

, but which a re intended to be overridden by other, more specialized classes.

The

1
toString()

method

The

1
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

1
+

operator to concatenate a string with an object or when you pass an object to a method that expects a string.

The default

1
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]":


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

Because this default method does not display much useful information, many classes define their own versions of

1
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

1
toString()

method are documented in the reference section.

The

1
toLocaleString()

Method

In addition to the basic

1
toString()

method, objects all have a

1
toLocaleString()

. The purpose of this method is to return a localized string representation of the object.

  • The default
    1
    toLocaleString()

    method defined by Object doesn't do any localization itself: it simply calls

    1
    toString()

    and returns that value.

  • The Date and Number classes define customized versions of
    1
    toLocaleString()

    that attempt to format numbers, dates, and times according to local conventions.

  • Array defines a
    1
    toLocaleString()

    method that works like

    1
    toString()

    except that it formats array elements by calling their

    1
    toLocaleString()

    methods instead of

    1
    toString()

    methods.

The

1
toJSON()

Method

1
Object.prototype

does not actually define a

1
toJSON()

method, but the

1
JSON.stringify()

method looks for a

1
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

1
valueOf()

Method

The

1
valueOf()

method is much like the

1
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

1
valueOf()

method does noting interesting, but some of the built-in classes define their own

1
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.