loading...

JavaScript – Object Attributes

Every object has associated prototype,
class, and extensible
attributes. The subsections that 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. (Review Prototypes and Inheritance for more
on prototypes and property inheritance.) 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 Prototypes that objects created from
object literals use Object.prototype as their prototype.
Objects created with new use the
value of the prototype property
of their constructor function 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. This is
explained in more detail in Classes and Constructors, which also explains why it is
not a completely reliable method for determining an object’s
prototype. Note that objects created by object literals or by
Object . create() have a constructor property that refers to the
Object() constructor. Thus,
constructor . prototype refers to the correct
prototype for object literals, but does not usually do so for
objects created with Object.create().

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};                    // Define a prototype object.
var o = Object.create(p);         // Create an object with that prototype.
p.isPrototypeOf(o)                // => true: o inherits from p
Object.prototype.isPrototypeOf(p) // => true: p inherits from Object.prototype

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

Mozilla’s implementation of JavaScript has (since the early
days of Netscape) exposed the prototype attribute through the
specially named __proto__
property, and you can use this property to directly query or set the
prototype of any object. Using __proto__ is not portable: it has not been
(and probably never will be) 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 its 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 set this attribute,
and there is only an indirect technique for querying it. The default
toString() method (inherited from
Object.prototype) returns a
string of the form:

[object class]

So to obtain the class of an object, you can invoke this
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 (see The call() and apply() Methods). Example 6-4 defines a
function that returns the class of any object you pass it.

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 toString() method is invoked on them, and
the function includes special cases for null and undefined. (The special cases are not
required in ECMAScript 5.) Objects created through built-in
constructors such as Array and
Date have
class attributes that match the names of 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 no way to
specify the class attribute for your own
classes of objects:

classof(null)       // => "Null"
classof(1)          // => "Number"
classof("")         // => "String"
classof(false)      // => "Boolean"
classof({})         // => "Object"
classof([])         // => "Array"
classof(/./)        // => "Regexp"
classof(new Date()) // => "Date"
classof(window)     // => "Window" (a client-side host object)
function f() {};    // Define a custom constructor
classof(new f());   // => "Object"

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 the
extensibility of an object. To determine whether an object 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 that 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 makes all of
the own properties of that object nonconfigurable. 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. 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 nonconfigurable, it also makes all
of the object’s own data properties read-only. (If the object has
accessor properties with setter methods, these are not affected and
can still be invoked by assignment to the property.) Use Object.isFrozen() to determine if an
object is frozen.

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 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 nonenumerable property
var o = Object.seal(Object.create(Object.freeze({x:1}),
                                  {y: {value: 2, writable: true}}));

Comments are closed.

loading...