buy the book to support the author.

Chapter 17. Objects and Inheritance

There are several layers to object-oriented programming (OOP) in JavaScript:

Each new layer only depends on prior ones, enabling you to learn JavaScript OOP incrementally. Layers 1 and 2 form a simple core that you can refer back to whenever you are getting confused by the more complicated layers 3 and 4.

Layer 1: Single Objects

Roughly, all objects in JavaScript are maps (dictionaries) from strings to values. A (key, value) entry in an object is called a property. The key of a property is always a text string. The value of a property can be any JavaScript value, including a function. Methods are properties whose values are functions.

Kinds of Properties

There are three kinds of properties:

  • Properties (or named data properties)
  • Normal properties in an object—that is, mappings from string keys to values. Named data properties include methods. This is by far the most common kind of property.
  • Accessors (or named accessor properties)
  • Special methods whose invocations look like reading or writing properties.Normal properties are storage locations for property values; accessors allow you to compute the values of properties. They are virtual properties, if you will. See Accessors (Getters and Setters) for details.
  • Internal properties
  • Exist only in the ECMAScript language specification. They are not directly accessible from JavaScript, but there might be indirect ways of accessing them. The specification writes the keys of internal properties in brackets. For example, [[Prototype]] holds the prototype of an object and is readable via Object.getPrototypeOf().

Object Literals

JavaScript’s object literals allow you to directly create plain objects (direct instances of Object). The following code uses an object literal to assign an object to the variable jane. The object has the two properties: name and describe. describe is a method:

  1. var jane = {
  2. name: 'Jane',
  3.  
  4. describe: function () {
  5. return 'Person named '+this.name; // (1)
  6. }, // (2)
  7. };
  • Use this in methods to refer to the current object (also called the receiver of a method invocation).
  • ECMAScript 5 allows a trailing comma (after the last property) in an object literal. Alas, not all older browsers support it. A trailing comma is useful, because you can rearrange properties without having to worry which property is last.

You may get the impression that objects are only maps from strings to values. But they are more than that: they are real general-purpose objects. For example, you can use inheritance between objects (see Layer 2: The Prototype Relationship Between Objects), and you can protect objects from being changed. The ability to directly create objects is one of JavaScript’s standout features: you can start with concrete objects (no classes needed!) and introduce abstractions later. For example, constructors, which are factories for objects (as discussed in Layer 3: Constructors—Factories for Instances), are roughly similar to classes in other languages.

Dot Operator (.): Accessing Properties via Fixed Keys

The dot operator provides a compact syntax for accessing properties. The property keys must be identifiers (consult Legal Identifiers).If you want to read or write properties with arbitrary names, you need to use the bracket operator (see Bracket Operator ([]): Accessing Properties via Computed Keys).

The examples in this section work with the following object:

  1. var jane = {
  2. name: 'Jane',
  3.  
  4. describe: function () {
  5. return 'Person named '+this.name;
  6. }
  7. };

Getting properties

The dot operator lets you “get” a property (read its value). Here are some examples:

  1. > jane.name // get property `name`
  2. 'Jane'
  3. > jane.describe // get property `describe`
  4. [Function]

Getting a property that doesn’t exist returns undefined:

  1. > jane.unknownProperty
  2. undefined

Calling methods

The dot operator is also used to call methods:

  1. > jane.describe() // call method `describe`
  2. 'Person named Jane'

Setting properties

You can use the assignment operator (=) to set the value of a property referred to via the dot notation. For example:

  1. > jane.name = 'John'; // set property `name`
  2. > jane.describe()
  3. 'Person named John'

If a property doesn’t exist yet, setting it automatically creates it. If a property already exists, setting it changes its value.

Deleting properties

The delete operator lets you completely remove a property (the whole key-value pair) from an object. For example:

  1. > var obj = { hello: 'world' };
  2. > delete obj.hello
  3. true
  4. > obj.hello
  5. undefined

If you merely set a property to undefined, the property still exists and the object still contains its key:

  1. > var obj = { foo: 'a', bar: 'b' };
  2.  
  3. > obj.foo = undefined;
  4. > Object.keys(obj)
  5. [ 'foo', 'bar' ]

If you delete the property, its key is gone, too:

  1. > delete obj.foo
  2. true
  3. > Object.keys(obj)
  4. [ 'bar' ]

delete affects only the direct (“own,” noninherited) properties of an object. Its prototypes are not touched (see Deleting an inherited property).

Tip

Use the delete operator sparingly. Most modern JavaScript engines optimize the performance of instances created by constructors if their “shape” doesn’t change (roughly: no properties are removed or added). Deleting a property prevents that optimization.

The return value of delete

delete returns false if the property is an own property, but cannot be deleted. It returns true in all other cases. Following are some examples.

As a preparation, we create one property that can be deleted and another one that can’t be deleted (Getting and Defining Properties via Descriptors explains Object.defineProperty()):

  1. var obj = {};
  2. Object.defineProperty(obj, 'canBeDeleted', {
  3. value: 123,
  4. configurable: true
  5. });
  6. Object.defineProperty(obj, 'cannotBeDeleted', {
  7. value: 456,
  8. configurable: false
  9. });

delete returns false for own properties that can’t be deleted:

  1. > delete obj.cannotBeDeleted
  2. false

delete returns true in all other cases:

  1. > delete obj.doesNotExist
  2. true
  3. > delete obj.canBeDeleted
  4. true

delete returns true even if it doesn’t change anything (inherited properties are never removed):

  1. > delete obj.toString
  2. true
  3. > obj.toString // still there
  4. [Function: toString]

Unusual Property Keys

While you can’t use reserved words (such as var and function) as variable names, you can use them as property keys:

  1. > var obj = { var: 'a', function: 'b' };
  2. > obj.var
  3. 'a'
  4. > obj.function
  5. 'b'

Numbers can be used as property keys in object literals, but they are interpreted as strings. The dot operator can only access properties whose keys are identifiers. Therefore, you need the bracket operator (shown in the following example) to access properties whose keys are numbers:

  1. > var obj = { 0.7: 'abc' };
  2. > Object.keys(obj)
  3. [ '0.7' ]
  4. > obj['0.7']
  5. 'abc'

Object literals also allow you to use arbitrary strings (that are neither identifiers nor numbers) as property keys, but you must quote them. Again, you need the bracket operator to access the property values:

  1. > var obj = { 'not an identifier': 123 };
  2. > Object.keys(obj)
  3. [ 'not an identifier' ]
  4. > obj['not an identifier']
  5. 123

Bracket Operator ([]): Accessing Properties via Computed Keys

While the dot operator works with fixed property keys, the bracket operator allows you to refer to a property via an expression.

Getting properties via the bracket operator

The bracket operator lets you compute the key of a property, via an expression:

  1. > var obj = { someProperty: 'abc' };
  2.  
  3. > obj['some' + 'Property']
  4. 'abc'
  5.  
  6. > var propKey = 'someProperty';
  7. > obj[propKey]
  8. 'abc'

That also allows you to access properties whose keys are not identifiers:

  1. > var obj = { 'not an identifier': 123 };
  2. > obj['not an identifier']
  3. 123

Note that the bracket operator coerces its interior to string. For example:

  1. > var obj = { '6': 'bar' };
  2. > obj[3+3] // key: the string '6'
  3. 'bar'

Calling methods via the bracket operator

Calling methods works as you would expect:

  1. > var obj = { myMethod: function () { return true } };
  2. > obj['myMethod']()
  3. true

Setting properties via the bracket operator

Setting properties works analogously to the dot operator:

  1. > var obj = {};
  2. > obj['anotherProperty'] = 'def';
  3. > obj.anotherProperty
  4. 'def'

Deleting properties via the bracket operator

Deleting properties also works similarly to the dot operator:

  1. > var obj = { 'not an identifier': 1, prop: 2 };
  2. > Object.keys(obj)
  3. [ 'not an identifier', 'prop' ]
  4. > delete obj['not an identifier']
  5. true
  6. > Object.keys(obj)
  7. [ 'prop' ]

Converting Any Value to an Object

It’s not a frequent use case, but sometimes you need to convert an arbitrary value to an object. Object(), used as a function (not as a constructor), provides that service. It produces the following results:

Value Result
(Called with no parameters) {}
undefined {}
null {}
A boolean bool new Boolean(bool)
A number num new Number(num)
A string str new String(str)
An object obj obj (unchanged, nothing to convert)

Here are some examples:

  1. > Object(null) instanceof Object
  2. true
  3.  
  4. > Object(false) instanceof Boolean
  5. true
  6.  
  7. > var obj = {};
  8. > Object(obj) === obj
  9. true

The following function checks whether value is an object:

  1. function isObject(value) {
  2. return value === Object(value);
  3. }

Note that the preceding function creates an object if value isn’t an object.You can implement the same function without doing that, via typeof (see Pitfall: typeof null).

You can also invoke Object as a constructor, which produces the same results as calling it as a function:

  1. > var obj = {};
  2. > new Object(obj) === obj
  3. true
  4.  
  5. > new Object(123) instanceof Number
  6. true

Tip

Avoid the constructor; an empty object literal is almost always a better choice:

  1. var obj = new Object(); // avoid
  2. var obj = {}; // prefer

this as an Implicit Parameter of Functions and Methods

When you call a function, this is always an (implicit) parameter:

  • Normal functions in sloppy mode
  • Even though normal functions have no use for this, it still exists as a special variable whose value is always the global object (window in browsers; see The Global Object):
  1. > function returnThisSloppy() { return this }
  2. > returnThisSloppy() === window
  3. true
  • Normal functions in strict mode
  • this is always undefined:
  1. > function returnThisStrict() { 'use strict'; return this }
  2. > returnThisStrict() === undefined
  3. true
  • Methods
  • this refers to the object on which the method has been invoked:
  1. > var obj = { method: returnThisStrict };
  2. > obj.method() === obj
  3. true

In the case of methods, the value of this is called the receiver of the method call.

Calling Functions While Setting this: call(), apply(), and bind()

Remember that functions are also objects. Thus, each function has methods of its own. Three of them are introduced in this section and help with calling functions. These three methods are used in the following sections to work around some of the pitfalls of calling functions. The upcoming examples all refer to the following object, jane:

  1. var jane = {
  2. name: 'Jane',
  3. sayHelloTo: function (otherName) {
  4. 'use strict';
  5. console.log(this.name+' says hello to '+otherName);
  6. }
  7. };

Function.prototype.call(thisValue, arg1?, arg2?, …)

The first parameter is the value that this will have inside the invoked function; the remaining parameters are handed over as arguments to the invoked function. The following three invocations are equivalent:

  1. jane.sayHelloTo('Tarzan');
  2.  
  3. jane.sayHelloTo.call(jane, 'Tarzan');
  4.  
  5. var func = jane.sayHelloTo;
  6. func.call(jane, 'Tarzan');

For the second invocation, you need to repeat jane, because call() doesn’t know how you got the function that it is invoked on.

Function.prototype.apply(thisValue, argArray)

The first parameter is the value that this will have inside the invoked function; the second parameter is an array that provides the arguments for the invocation. The following three invocations are equivalent:

  1. jane.sayHelloTo('Tarzan');
  2.  
  3. jane.sayHelloTo.apply(jane, ['Tarzan']);
  4.  
  5. var func = jane.sayHelloTo;
  6. func.apply(jane, ['Tarzan']);

For the second invocation, you need to repeat jane, because apply() doesn’t know how you got the function that it is invoked on.

apply() for Constructors explains how to use apply() with constructors.

Function.prototype.bind(thisValue, arg1?, …, argN?)

This method performs partial function application—meaning it creates a new function that calls the receiver of bind() in the following manner: the value of this is thisValue and the arguments start with arg1 until argN, followed by the arguments of the new function. In other words, the new function appends its arguments to arg1, …, argN when it calls the original function.Let’s look at an example:

  1. function func() {
  2. console.log('this: '+this);
  3. console.log('arguments: '+Array.prototype.slice.call(arguments));
  4. }
  5. var bound = func.bind('abc', 1, 2);

The array method slice is used to convert arguments to an array, which is necessary for logging it (this operation is explained in Array-Like Objects and Generic Methods). bound is a new function. Here’s the interaction:

  1. > bound(3)
  2. this: abc
  3. arguments: 1,2,3

The following three invocations of sayHelloTo are all equivalent:

  1. jane.sayHelloTo('Tarzan');
  2.  
  3. var func1 = jane.sayHelloTo.bind(jane);
  4. func1('Tarzan');
  5.  
  6. var func2 = jane.sayHelloTo.bind(jane, 'Tarzan');
  7. func2();

apply() for Constructors

Let’s pretend that JavaScript has a triple dot operator () that turns arrays into actual parameters. Such an operator would allow you to use Math.max() (see Other Functions) with arrays. In that case, the following two expressions would be equivalent:

  1. Math.max(...[13, 7, 30])
  2. Math.max(13, 7, 30)

For functions, you can achieve the effect of the triple dot operator via apply():

  1. > Math.max.apply(null, [13, 7, 30])
  2. 30

The triple dot operator would also make sense for constructors:

  1. new Date(...[2011, 11, 24]) // Christmas Eve 2011

Alas, here apply() does not work, because it helps only with function or method calls, not with constructor invocations.

Manually simulating an apply() for constructors

We can simulate apply() in two steps.

  • Step 1
  • Pass the arguments to Date via a method call (they are not in an array—yet):
  1. new (Date.bind(null, 2011, 11, 24))

The preceding code uses bind() to create a constructor without parameters and invokes it via new.

  • Step 2
  • Use apply() to hand an array to bind(). Because bind() is a method call, we can use apply():
  1. new (Function.prototype.bind.apply(
  2. Date, [null, 2011, 11, 24]))

The preceding array contains null, followed by the elements of arr. We can use concat() to create it by prepending null to arr:

  1. var arr = [2011, 11, 24];
  2. new (Function.prototype.bind.apply(
  3. Date, [null].concat(arr)))

A library method

The preceding manual workaround is inspired by a library method published by Mozilla. The following is a slightly edited version of it:

  1. if (!Function.prototype.construct) {
  2. Function.prototype.construct = function(argArray) {
  3. if (! Array.isArray(argArray)) {
  4. throw new TypeError("Argument must be an array");
  5. }
  6. var constr = this;
  7. var nullaryFunc = Function.prototype.bind.apply(
  8. constr, [null].concat(argArray));
  9. return new nullaryFunc();
  10. };
  11. }

Here is the method in use:

  1. > Date.construct([2011, 11, 24])
  2. Sat Dec 24 2011 00:00:00 GMT+0100 (CET)

An alternative approach

An alternative to the previous approach is to create an uninitialized instance via Object.create() and then call the constructor (as a function) via apply(). That means that you are effectively reimplementing the new operator (some checks are omitted):

  1. Function.prototype.construct = function(argArray) {
  2. var constr = this;
  3. var inst = Object.create(constr.prototype);
  4. var result = constr.apply(inst, argArray); // (1)
  5.  
  6. // Check: did the constructor return an object
  7. // and prevent `this` from being the result?
  8. return result ? result : inst;
  9. };

Warning

The preceding code does not work for most built-in constructors, which always produce new instances when called as functions. In other words, the step in line (1) doesn’t set up inst as desired.

Pitfall: Losing this When Extracting a Method

If you extract a method from an object, it becomes a true function again. Its connection with the object is severed, and it usually doesn’t work properly anymore. Take, for example, the following object, counter:

  1. var counter = {
  2. count: 0,
  3. inc: function () {
  4. this.count++;
  5. }
  6. }

Extracting inc and calling it (as a function!) fails:

  1. > var func = counter.inc;
  2. > func()
  3. > counter.count // didn’t work
  4. 0

Here’s the explanation: we have called the value of counter.inc as a function. Hence, this is the global object and we have performed window.count++. window.count does not exist and is undefined. Applying the ++ operator to it sets it to NaN:

  1. > count // global variable
  2. NaN

How to get a warning

If method inc() is in strict mode, you get a warning:

  1. > counter.inc = function () { 'use strict'; this.count++ };
  2. > var func2 = counter.inc;
  3. > func2()
  4. TypeError: Cannot read property 'count' of undefined

The reason is that when we call the strict mode function func2, this is undefined, resulting in an error.

How to properly extract a method

Thanks to bind(), we can make sure that inc doesn’t lose the connection with counter:

  1. > var func3 = counter.inc.bind(counter);
  2. > func3()
  3. > counter.count // it worked!
  4. 1

Callbacks and extracted methods

In JavaScript, there are many functions and methods that accept callbacks. Examples in browsers are setTimeout() and event handling. If we pass in counter.inc as a callback, it is also invoked as a function, resulting in the same problem just described. To illustrate this phenomenon, let’s use a simple callback-invoking function:

  1. function callIt(callback) {
  2. callback();
  3. }

Executing counter.count via callIt triggers a warning (due to strict mode):

  1. > callIt(counter.inc)
  2. TypeError: Cannot read property 'count' of undefined

As before, we fix things via bind():

  1. > callIt(counter.inc.bind(counter))
  2. > counter.count // one more than before
  3. 2

Warning

Each call to bind() creates a new function. That has consequences when you’re registering and unregistering callbacks (e.g., for event handling). You need to store the value you registered somewhere and use it for unregistering, too.

Pitfall: Functions Inside Methods Shadow this

You often nest function definitions in JavaScript, because functions can be parameters (e.g., callbacks) and because they can be created in place, via function expressions. This poses a problem when a method contains a normal function and you want to access the former’s this inside the latter, because the method’s this is shadowed by the normal function’s this (which doesn’t even have any use for its own this).In the following example, the function at (1) tries to access the method’s this at (2):

  1. var obj = {
  2. name: 'Jane',
  3. friends: [ 'Tarzan', 'Cheeta' ],
  4. loop: function () {
  5. 'use strict';
  6. this.friends.forEach(
  7. function (friend) { // (1)
  8. console.log(this.name+' knows '+friend); // (2)
  9. }
  10. );
  11. }
  12. };

This fails, because the function at (1) has its own this, which is undefined here:

  1. > obj.loop();
  2. TypeError: Cannot read property 'name' of undefined

There are three ways to work around this problem.

Workaround 1: that = this

We assign this to a variable that won’t be shadowed inside the nested function:

  1. loop: function () {
  2. 'use strict';
  3. var that = this;
  4. this.friends.forEach(function (friend) {
  5. console.log(that.name+' knows '+friend);
  6. });
  7. }

Here’s the interaction:

  1. > obj.loop();
  2. Jane knows Tarzan
  3. Jane knows Cheeta

Workaround 2: bind()

We can use bind() to give the callback a fixed value for this—namely, the method’s this (line (1)):

  1. loop: function () {
  2. 'use strict';
  3. this.friends.forEach(function (friend) {
  4. console.log(this.name+' knows '+friend);
  5. }.bind(this)); // (1)
  6. }

Workaround 3: a thisValue for forEach()

A workaround that is specific to forEach() (see Examination Methods) is to provide a second parameter after the callback that becomes the this of the callback:

  1. loop: function () {
  2. 'use strict';
  3. this.friends.forEach(function (friend) {
  4. console.log(this.name+' knows '+friend);
  5. }, this);
  6. }

Layer 2: The Prototype Relationship Between Objects

The prototype relationship between two objects is about inheritance: every object can have another object as its prototype. Then the former object inherits all of its prototype’s properties.An object specifies its prototype via the internal property [[Prototype]]. Every object has this property, but it can be null. The chain of objects connected by the [[Prototype]] property is called the prototype chain (Figure 17-1).

A prototype chain.

Figure 17-1. A prototype chain.

To see how prototype-based (or prototypal) inheritance works, let’s look at an example (with invented syntax for specifying the [[Prototype]] property):

  1. var proto = {
  2. describe: function () {
  3. return 'name: '+this.name;
  4. }
  5. };
  6. var obj = {
  7. [[Prototype]]: proto,
  8. name: 'obj'
  9. };

The object obj inherits the property describe from proto. It also has a so-called own (noninherited, direct) property, name.

Inheritance

obj inherits the property describe; you can access it as if the object itself had that property:

  1. > obj.describe
  2. [Function]

Whenever you access a property via obj, JavaScript starts the search for it in that object and continues with its prototype, the prototype’s prototype, and so on. That’s why we can access proto.describe via obj.describe. The prototype chain behaves as if it were a single object. That illusion is maintained when you call a method: the value of this is always the object where the search for the method began, not where the method was found. That allows the method to access all of the properties of the prototype chain. For example:

  1. > obj.describe()
  2. 'name: obj'

Inside describe(), this is obj, which allows the method to access obj.name.

Overriding

In a prototype chain, a property in an object overrides a property with the same key in a “later” object: the former property is found first. It hides the latter property, which can’t be accessed anymore.As an example, let’s override the method proto.describe() in obj:

  1. > obj.describe = function () { return 'overridden' };
  2. > obj.describe()
  3. 'overridden'

That is similar to how overriding of methods works in class-based languages.

Sharing Data Between Objects via a Prototype

Prototypes are great for sharing data between objects: several objects get the same prototype, which holds all shared properties. Let’s look at an example. The objects jane and tarzan both contain the same method, describe(). That is something that we would like to avoid by using sharing:

  1. var jane = {
  2. name: 'Jane',
  3. describe: function () {
  4. return 'Person named '+this.name;
  5. }
  6. };
  7. var tarzan = {
  8. name: 'Tarzan',
  9. describe: function () {
  10. return 'Person named '+this.name;
  11. }
  12. };

Both objects are persons. Their name property is different, but we could have them share the method describe. We do that by creating a common prototype called PersonProto and putting describe into it (Figure 17-2).

The objects jane and tarzan share the prototype PersonProto and thus the property describe.

Figure 17-2. The objects jane and tarzan share the prototype PersonProto and thus the property describe.

The following code creates objects jane and tarzan that share the prototype PersonProto:

  1. var PersonProto = {
  2. describe: function () {
  3. return 'Person named '+this.name;
  4. }
  5. };
  6. var jane = {
  7. [[Prototype]]: PersonProto,
  8. name: 'Jane'
  9. };
  10. var tarzan = {
  11. [[Prototype]]: PersonProto,
  12. name: 'Tarzan'
  13. };

And here is the interaction:

  1. > jane.describe()
  2. Person named Jane
  3. > tarzan.describe()
  4. Person named Tarzan

This is a common pattern: the data resides in the first object of a prototype chain, while methods reside in later objects. JavaScript’s flavor of prototypal inheritance is designed to support this pattern: setting a property affects only the first object in a prototype chain, whereas getting a property considers the complete chain (see Setting and Deleting Affects Only Own Properties).

Getting and Setting the Prototype

So far, we have pretended that you can access the internal property [[Prototype]] from JavaScript. But the language does not let you do that. Instead, there are functions for reading the prototype and for creating a new object with a given prototype.

Creating a new object with a given prototype

This invocation:

  1. Object.create(proto, propDescObj?)

creates an object whose prototype is proto. Optionally, properties can be added via descriptors (which are explained in Property Descriptors). In the following example, object jane gets the prototype PersonProto and a mutable property name whose value is 'Jane' (as specified via a property descriptor):

  1. var PersonProto = {
  2. describe: function () {
  3. return 'Person named '+this.name;
  4. }
  5. };
  6. var jane = Object.create(PersonProto, {
  7. name: { value: 'Jane', writable: true }
  8. });

Here is the interaction:

  1. > jane.describe()
  2. 'Person named Jane'

But you frequently just create an empty object and then manually add properties, because descriptors are verbose:

  1. var jane = Object.create(PersonProto);
  2. jane.name = 'Jane';

Reading the prototype of an object

This method call:

  1. Object.getPrototypeOf(obj)

returns the prototype of obj. Continuing the preceding example:

  1. > Object.getPrototypeOf(jane) === PersonProto
  2. true

Checking whether one object a prototype of another one

This syntax:

  1. Object.prototype.isPrototypeOf(obj)

checks whether the receiver of the method is a (direct or indirect) prototype of obj. In other words: are the receiver and obj in the same prototype chain, and does obj come before the receiver? For example:

  1. > var A = {};
  2. > var B = Object.create(A);
  3. > var C = Object.create(B);
  4. > A.isPrototypeOf(C)
  5. true
  6. > C.isPrototypeOf(A)
  7. false

Finding the object where a property is defined

The following function iterates over the property chain of an object obj. It returns the first object that has an own property with the key propKey, or null if there is no such object:

  1. function getDefiningObject(obj, propKey) {
  2. obj = Object(obj); // make sure it’s an object
  3. while (obj && !{}.hasOwnProperty.call(obj, propKey)) {
  4. obj = Object.getPrototypeOf(obj);
  5. // obj is null if we have reached the end
  6. }
  7. return obj;
  8. }

In the preceding code, we called the method Object.prototype.hasOwnProperty generically (see Generic Methods: Borrowing Methods from Prototypes).

The Special Property proto

Some JavaScript engines have a special property for getting and setting the prototype of an object: proto. It brings direct access to [[Prototype]] to the language:

  1. > var obj = {};
  2.  
  3. > obj.__proto__ === Object.prototype
  4. true
  5.  
  6. > obj.__proto__ = Array.prototype
  7. > Object.getPrototypeOf(obj) === Array.prototype
  8. true

There are several things you need to know about proto:

  • proto is pronounced “dunder proto,” an abbreviation of “double underscore proto.” That pronunciation has been borrowed from the Python programming language (as suggested by Ned Batchelder in 2006). Special variables with double underscores are quite frequent in Python.
  • proto is not part of the ECMAScript 5 standard. Therefore, you must not use it if you want your code to conform to that standard and run reliably across current JavaScript engines.
  • However, more and more engines are adding support for proto and it will be part of ECMAScript 6.
  • The following expression checks whether an engine supports proto as a special property:
  1. Object.getPrototypeOf({ __proto__: null }) === null

Setting and Deleting Affects Only Own Properties

Only getting a property considers the complete prototype chain of an object. Setting and deleting ignores inheritance and affects only own properties.

Setting a property

Setting a property creates an own property, even if there is an inherited property with that key. For example, given the following source code:

  1. var proto = { foo: 'a' };
  2. var obj = Object.create(proto);

obj inherits foo from proto:

  1. > obj.foo
  2. 'a'
  3. > obj.hasOwnProperty('foo')
  4. false

Setting foo has the desired result:

  1. > obj.foo = 'b';
  2. > obj.foo
  3. 'b'

However, we have created an own property and not changed proto.foo:

  1. > obj.hasOwnProperty('foo')
  2. true
  3. > proto.foo
  4. 'a'

The rationale is that prototype properties are meant to be shared by several objects. This approach allows us to nondestructively “change” them—only the current object is affected.

Deleting an inherited property

You can only delete own properties. Let’s again set up an object, obj, with a prototype, proto:

  1. var proto = { foo: 'a' };
  2. var obj = Object.create(proto);

Deleting the inherited property foo has no effect:

  1. > delete obj.foo
  2. true
  3. > obj.foo
  4. 'a'

For more information on the delete operator, consult Deleting properties.

Changing properties anywhere in the prototype chain

If you want to change an inherited property, you first have to find the object that owns it (see Finding the object where a property is defined) and then perform the change on that object. For example, let’s delete the property foo from the previous example:

  1. > delete getDefiningObject(obj, 'foo').foo;
  2. true
  3. > obj.foo
  4. undefined

Iteration and Detection of Properties

Operations for iterating over and detecting properties are influenced by:

  • Inheritance (own properties versus inherited properties)
  • An own property of an object is stored directly in that object. An inherited property is stored in one of its prototypes.
  • Enumerability (enumerable properties versus nonenumerable properties)
  • The enumerability of a property is an attribute (see Property Attributes and Property Descriptors), a flag that can be true or false. Enumerability rarely matters and can normally be ignored (see Enumerability: Best Practices).

You can list own property keys, list all enumerable property keys, and check whether a property exists. The following subsections show how.

Listing Own Property Keys

You can either list all own property keys, or only enumerable ones:

  • Object.getOwnPropertyNames(obj)returns the keys of all own properties of obj.
  • Object.keys(obj)returns the keys of all enumerable own properties of obj.

Note that properties are normally enumerable (see Enumerability: Best Practices), so you can use Object.keys(), especially for objects that you have created.

Listing All Property Keys

If you want to list all properties (both own and inherited ones) of an object, then you have two options.

Option 1 is to use the loop:

  1. for («variable» in «object»)
  2. «statement»

to iterate over the keys of all enumerable properties of object. See for-in for a more thorough description.

Option 2 is to implement a function yourself that iterates over all properties (not just enumerable ones). For example:

  1. function getAllPropertyNames(obj) {
  2. var result = [];
  3. while (obj) {
  4. // Add the own property names of `obj` to `result`
  5. result = result.concat(Object.getOwnPropertyNames(obj));
  6. obj = Object.getPrototypeOf(obj);
  7. }
  8. return result;
  9. }

Checking Whether a Property Exists

You can check whether an object has a property, or whether a property exists directly inside an object:

  • propKey in obj
  • Returns true if obj has a property whose key is propKey. Inherited properties are included in this test.
  • Object.prototype.hasOwnProperty(propKey)
  • Returns true if the receiver (this) has an own (noninherited) property whose key is propKey.

Warning

Avoid invoking hasOwnProperty() directly on an object, as it may be overridden (e.g., by an own property whose key is hasOwnProperty):

  1. > var obj = { hasOwnProperty: 1, foo: 2 };
  2. > obj.hasOwnProperty('foo') // unsafe
  3. TypeError: Property 'hasOwnProperty' is not a function

Instead, it is better to call it generically (see Generic Methods: Borrowing Methods from Prototypes):

  1. > Object.prototype.hasOwnProperty.call(obj, 'foo') // safe
  2. true
  3. > {}.hasOwnProperty.call(obj, 'foo') // shorter
  4. true

Examples

The following examples are based on these definitions:

  1. var proto = Object.defineProperties({}, {
  2. protoEnumTrue: { value: 1, enumerable: true },
  3. protoEnumFalse: { value: 2, enumerable: false }
  4. });
  5. var obj = Object.create(proto, {
  6. objEnumTrue: { value: 1, enumerable: true },
  7. objEnumFalse: { value: 2, enumerable: false }
  8. });

Object.defineProperties() is explained in Getting and Defining Properties via Descriptors, but it should be fairly obvious how it works: proto has the own properties protoEnumTrue and protoEnumFalse and obj has the own properties objEnumTrue and objEnumFalse (and inherits all of proto’s properties).

Note

Note that objects (such as proto in the preceding example) normally have at least the prototype Object.prototype (where standard methods such as toString() and hasOwnProperty() are defined):

  1. > Object.getPrototypeOf({}) === Object.prototype
  2. true

The effects of enumerability

Among property-related operations, enumberability only influences the for-in loop and Object.keys() (it also influences JSON.stringify(), see JSON.stringify(value, replacer?, space?)).

The for-in loop iterates over the keys of all enumerable properties, including inherited ones (note that none of the nonenumerable properties of Object.prototype show up):

  1. > for (var x in obj) console.log(x);
  2. objEnumTrue
  3. protoEnumTrue

Object.keys() returns the keys of all own (noninherited) enumerable properties:

  1. > Object.keys(obj)
  2. [ 'objEnumTrue' ]

If you want the keys of all own properties, you need to use Object.getOwnPropertyNames():

  1. > Object.getOwnPropertyNames(obj)
  2. [ 'objEnumTrue', 'objEnumFalse' ]

The effects of inheritance

Only the for-in loop (see the previous example) and the in operator consider inheritance:

  1. > 'toString' in obj
  2. true
  3. > obj.hasOwnProperty('toString')
  4. false
  5. > obj.hasOwnProperty('objEnumFalse')
  6. true

Computing the number of own properties of an object

Objects don’t have a method such as length or size, so you have to use the following workaround:

  1. Object.keys(obj).length

Best Practices: Iterating over Own Properties

To iterate over property keys:

  • Combine for-in with hasOwnProperty(), in the manner described in for-in. This works even on older JavaScript engines. For example:
  1. for (var key in obj) {
  2. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  3. console.log(key);
  4. }
  5. }
  • Combine Object.keys() or Object.getOwnPropertyNames() with forEach() array iteration:
  1. var obj = { first: 'John', last: 'Doe' };
  2. // Visit non-inherited enumerable keys
  3. Object.keys(obj).forEach(function (key) {
  4. console.log(key);
  5. });

To iterate over property values or over (key, value) pairs:

  • Iterate over the keys, and use each key to retrieve the corresponding value. Other languages make this simpler, but not JavaScript.

Accessors (Getters and Setters)

ECMAScript 5 lets you write methods whose invocations look like you are getting or setting a property. That means that a property is virtual and not storage space. You could, for example, forbid setting a property and always compute the value returned when reading it.

Defining Accessors via an Object Literal

The following example uses an object literal to define a setter and a getter for property foo:

  1. var obj = {
  2. get foo() {
  3. return 'getter';
  4. },
  5. set foo(value) {
  6. console.log('setter: '+value);
  7. }
  8. };

Here’s the interaction:

  1. > obj.foo = 'bla';
  2. setter: bla
  3. > obj.foo
  4. 'getter'

Defining Accessors via Property Descriptors

An alternate way to specify getters and setters is via property descriptors (see Property Descriptors). The following code defines the same object as the preceding literal:

  1. var obj = Object.create(
  2. Object.prototype, { // object with property descriptors
  3. foo: { // property descriptor
  4. get: function () {
  5. return 'getter';
  6. },
  7. set: function (value) {
  8. console.log('setter: '+value);
  9. }
  10. }
  11. }
  12. );

Accessors and Inheritance

Getters and setters are inherited from prototypes:

  1. > var proto = { get foo() { return 'hello' } };
  2. > var obj = Object.create(proto);
  3.  
  4. > obj.foo
  5. 'hello'

Property Attributes and Property Descriptors

Tip

Property attributes and property descriptors are an advanced topic. You normally don’t need to know how they work.

In this section, we’ll look at the internal structure of properties:

  • Property attributes are the atomic building blocks of properties.
  • A property descriptor is a data structure for working programmatically with attributes.

Property Attributes

All of a property’s state, both its data and its metadata, is stored in attributes. They are fields that a property has, much like an object has properties. Attribute keys are often written in double brackets. Attributes matter for normal properties and for accessors (getters and setters).

The following attributes are specific to normal properties:

  • [[Value]] holds the property’s value, its data.
  • [[Writable]] holds a boolean indicating whether the value of a property can be changed.

The following attributes are specific to accessors:

  • [[Get]] holds the getter, a function that is called when a property is read. The function computes the result of the read access.
  • [[Set]] holds the setter, a function that is called when a property is set to a value. The function receives that value as a parameter.

All properties have the following attributes:

  • [[Enumerable]] holds a boolean. Making a property nonenumerable hides it from some operations (see Iteration and Detection of Properties).
  • [[Configurable]] holds a boolean. If it is false, you cannot delete a property, change any of its attributes (except [[Value]]), or convert it from a data property to an accessor property or vice versa. In other words, [[Configurable]] controls the writability of a property’s metadata. There is one exception to this rule—JavaScript allows you to change an unconfigurable property from writable to read-only, for historic reasons; the property length of arrays has always been writable and unconfigurable. Without this exception, you wouldn’t be able to freeze (see Freezing) arrays.

Default values

If you don’t specify attributes, the following defaults are used:

Attribute key Default value
[[Value]] undefined
[[Get]] undefined
[[Set]] undefined
[[Writable]] false
[[Enumerable]] false
[[Configurable]] false

These defaults are important when you are creating properties via property descriptors (see the following section).

Property Descriptors

A property descriptor is a data structure for working programmatically with attributes.It is an object that encodes the attributes of a property. Each of a descriptor’s properties corresponds to an attribute. For example, the following is the descriptor of a read-only property whose value is 123:

  1. {
  2. value: 123,
  3. writable: false,
  4. enumerable: true,
  5. configurable: false
  6. }

You can achieve the same goal, immutability, via accessors. Then the descriptor looks as follows:

  1. {
  2. get: function () { return 123 },
  3. enumerable: true,
  4. configurable: false
  5. }

Getting and Defining Properties via Descriptors

Property descriptors are used for two kinds of operations:

  • Getting a property
  • All attributes of a property are returned as a descriptor.
  • Defining a property
  • Defining a property means something different depending on whether a property already exists:
  • If a property does not exist, create a new property whose attributes are as specified by the descriptor. If an attribute has no corresponding property in the descriptor, then use the default value. The defaults are dictated by what the attribute names mean. They are the opposite of the values that are used when creating a property via assignment (then the property is writable, enumerable, and configurable).For example:
  1. > var obj = {};
  2. > Object.defineProperty(obj, 'foo', { configurable: true });
  3. > Object.getOwnPropertyDescriptor(obj, 'foo')
  4. { value: undefined,
  5. writable: false,
  6. enumerable: false,
  7. configurable: true }

I usually don’t rely on the defaults and explicitly state all attributes, to be completely clear.

  • If a property already exists, update the attributes of the property as specified by the descriptor. If an attribute has no corresponding property in the descriptor, then don’t change it. Here is an example (continued from the previous one):
  1. > Object.defineProperty(obj, 'foo', { writable: true });
  2. > Object.getOwnPropertyDescriptor(obj, 'foo')
  3. { value: undefined,
  4. writable: true,
  5. enumerable: false,
  6. configurable: true }

The following operations allow you to get and set a property’s attributes via property descriptors:

  • Object.getOwnPropertyDescriptor(obj, propKey)
  • Returns the descriptor of the own (noninherited) property of obj whose key is propKey. If there is no such property, undefined is returned:
  1. > Object.getOwnPropertyDescriptor(Object.prototype, 'toString')
  2. { value: [Function: toString],
  3. writable: true,
  4. enumerable: false,
  5. configurable: true }
  6.  
  7. > Object.getOwnPropertyDescriptor({}, 'toString')
  8. undefined
  • Object.defineProperty(obj, propKey, propDesc)
  • Create or change a property of obj whose key is propKey and whose attributes are specified via propDesc. Return the modified object. For example:
  1. var obj = Object.defineProperty({}, 'foo', {
  2. value: 123,
  3. enumerable: true
  4. // writable: false (default value)
  5. // configurable: false (default value)
  6. });
  • Object.defineProperties(obj, propDescObj)
  • The batch version of Object.defineProperty(). Each property of propDescObj holds a property descriptor. The keys of the properties and their values tell Object.defineProperties what properties to create or change on obj. For example:
  1. var obj = Object.defineProperties({}, {
  2. foo: { value: 123, enumerable: true },
  3. bar: { value: 'abc', enumerable: true }
  4. });
  • Object.create(proto, propDescObj?)
  • First, create an object whose prototype is proto. Then, if the optional parameter propDescObj has been specified, add properties to it—in the same manner as Object.defineProperties. Finally, return the result. For example, the following code snippet produces the same result as the previous snippet:
  1. var obj = Object.create(Object.prototype, {
  2. foo: { value: 123, enumerable: true },
  3. bar: { value: 'abc', enumerable: true }
  4. });

Copying an Object

To create an identical copy of an object, you need to get two things right:

The following function performs such a copy:

  1. function copyObject(orig) {
  2. // 1. copy has same prototype as orig
  3. var copy = Object.create(Object.getPrototypeOf(orig));
  4.  
  5. // 2. copy has all of orig’s properties
  6. copyOwnPropertiesFrom(copy, orig);
  7.  
  8. return copy;
  9. }

The properties are copied from orig to copy via this function:

  1. function copyOwnPropertiesFrom(target, source) {
  2. Object.getOwnPropertyNames(source) // (1)
  3. .forEach(function(propKey) { // (2)
  4. var desc = Object.getOwnPropertyDescriptor(source, propKey); // (3)
  5. Object.defineProperty(target, propKey, desc); // (4)
  6. });
  7. return target;
  8. };

These are the steps involved:

  • Get an array with the keys of all own properties of source.
  • Iterate over those keys.
  • Retrieve a property descriptor.
  • Use that property descriptor to create an own property in target.

Note that this function is very similar to the function _.extend() in the Underscore.js library.

Properties: Definition Versus Assignment

The following two operations are very similar:

There are, however, a few subtle differences:

  • Defining a property means creating a new own property or updating the attributes of an existing own property. In both cases, the prototype chain is completely ignored.
  • Assigning to a property prop means changing an existing property. The process is as follows:
  • If prop is a setter (own or inherited), call that setter.
  • Otherwise, if prop is read-only (own or inherited), throw an exception (in strict mode) or do nothing (in sloppy mode). The next section explains this (slightly unexpected) phenomenon in more detail.
  • Otherwise, if prop is own (and writable), change the value of that property.
  • Otherwise, there either is no property prop, or it is inherited and writable. In both cases, define an own property prop that is writable, configurable, and enumerable. In the latter case, we have just overridden an inherited property (nondestructively changed it). In the former case, a missing property has been defined automatically. This kind of autodefining is problematic, because typos in assignments can be hard to detect.

Inherited Read-Only Properties Can’t Be Assigned To

If an object, obj, inherits a property, foo, from a prototype and foo is not writable, then you can’t assign to obj.foo:

  1. var proto = Object.defineProperty({}, 'foo', {
  2. value: 'a',
  3. writable: false
  4. });
  5. var obj = Object.create(proto);

obj inherits the read-only property foo from proto. In sloppy mode, setting the property has no effect:

  1. > obj.foo = 'b';
  2. > obj.foo
  3. 'a'

In strict mode, you get an exception:

  1. > (function () { 'use strict'; obj.foo = 'b' }());
  2. TypeError: Cannot assign to read-only property 'foo'

This fits with the idea that assignment changes inherited properties, but nondestructively. If an inherited property is read-only, you want to forbid all changes, even nondestructive ones.

Note that you can circumvent this protection by defining an own property (see the previous subsection for the difference between definition and assignment):

  1. > Object.defineProperty(obj, 'foo', { value: 'b' });
  2. > obj.foo
  3. 'b'

Enumerability: Best Practices

The general rule is that properties created by the system are nonenumerable, while properties created by users are enumerable:

  1. > Object.keys([])
  2. []
  3. > Object.getOwnPropertyNames([])
  4. [ 'length' ]
  5.  
  6. > Object.keys(['a'])
  7. [ '0' ]

This is especially true for the methods of the built-in instance prototypes:

  1. > Object.keys(Object.prototype)
  2. []
  3. > Object.getOwnPropertyNames(Object.prototype)
  4. [ hasOwnProperty',
  5. 'valueOf',
  6. 'constructor',
  7. 'toLocaleString',
  8. 'isPrototypeOf',
  9. 'propertyIsEnumerable',
  10. 'toString' ]

The main purpose of enumerability is to tell the for-in loop which properties it should ignore. As we have seen just now when we looked at instances of built-in constructors, everything not created by the user is hidden from for-in.

The only operations affected by enumerability are:

Here are some best practices to keep in mind:

  • For your own code, you can usually ignore enumerability and should avoid the for-in loop (Best Practices: Iterating over Arrays).
  • You normally shouldn’t add properties to built-in prototypes and objects. But if you do, you should make them nonenumerable to avoid breaking existing code.

Protecting Objects

There are three levels of protecting an object, listed here from weakest to strongest:

  • Preventing extensions
  • Sealing
  • Freezing

Preventing Extensions

Preventing extensions via:

  1. Object.preventExtensions(obj)

makes it impossible to add properties to obj. For example:

  1. var obj = { foo: 'a' };
  2. Object.preventExtensions(obj);

Now adding a property fails silently in sloppy mode:

  1. > obj.bar = 'b';
  2. > obj.bar
  3. undefined

and throws an error in strict mode:

  1. > (function () { 'use strict'; obj.bar = 'b' }());
  2. TypeError: Can't add property bar, object is not extensible

You can still delete properties, though:

  1. > delete obj.foo
  2. true
  3. > obj.foo
  4. undefined

You check whether an object is extensible via:

  1. Object.isExtensible(obj)

Sealing

Sealing via:

  1. Object.seal(obj)

prevents extensions and makes all properties “unconfigurable.” The latter means that the attributes (see Property Attributes and Property Descriptors) of properties can’t be changed anymore. For example, read-only properties stay read-only forever.

The following example demonstrates that sealing makes all properties unconfigurable:

  1. > var obj = { foo: 'a' };
  2.  
  3. > Object.getOwnPropertyDescriptor(obj, 'foo') // before sealing
  4. { value: 'a',
  5. writable: true,
  6. enumerable: true,
  7. configurable: true }
  8.  
  9. > Object.seal(obj)
  10.  
  11. > Object.getOwnPropertyDescriptor(obj, 'foo') // after sealing
  12. { value: 'a',
  13. writable: true,
  14. enumerable: true,
  15. configurable: false }

You can still change the property foo:

  1. > obj.foo = 'b';
  2. 'b'
  3. > obj.foo
  4. 'b'

but you can’t change its attributes:

  1. > Object.defineProperty(obj, 'foo', { enumerable: false });
  2. TypeError: Cannot redefine property: foo

You check whether an object is sealed via:

  1. Object.isSealed(obj)

Freezing

Freezing is performed via:

  1. Object.freeze(obj)

It makes all properties nonwritable and seals obj. In other words, obj is not extensible and all properties are read-only, and there is no way to change that. Let’s look at an example:

  1. var point = { x: 17, y: -5 };
  2. Object.freeze(point);

Once again, you get silent failures in sloppy mode:

  1. > point.x = 2; // no effect, point.x is read-only
  2. > point.x
  3. 17
  4.  
  5. > point.z = 123; // no effect, point is not extensible
  6. > point
  7. { x: 17, y: -5 }

And you get errors in strict mode:

  1. > (function () { 'use strict'; point.x = 2 }());
  2. TypeError: Cannot assign to read-only property 'x'
  3.  
  4. > (function () { 'use strict'; point.z = 123 }());
  5. TypeError: Can't add property z, object is not extensible

You check whether an object is frozen via:

  1. Object.isFrozen(obj)

Pitfall: Protection Is Shallow

Protecting an object is shallow: it affects the own properties, but not the values of those properties. For example, consider the following object:

  1. var obj = {
  2. foo: 1,
  3. bar: ['a', 'b']
  4. };
  5. Object.freeze(obj);

Even though you have frozen obj, it is not completely immutable—you can change the (mutable) value of property bar:

  1. > obj.foo = 2; // no effect
  2. > obj.bar.push('c'); // changes obj.bar
  3.  
  4. > obj
  5. { foo: 1, bar: [ 'a', 'b', 'c' ] }

Additionally, obj has the prototype Object.prototype, which is also mutable.

Layer 3: Constructors—Factories for Instances

A constructor function (short: constructor) helps with producing objects that are similar in some way. It is a normal function, but it is named, set up, and invoked differently. This section explains how constructors work. They correspond to classes in other languages.

We have already seen an example of two objects that are similar (in Sharing Data Between Objects via a Prototype):

  1. var PersonProto = {
  2. describe: function () {
  3. return 'Person named '+this.name;
  4. }
  5. };
  6. var jane = {
  7. [[Prototype]]: PersonProto,
  8. name: 'Jane'
  9. };
  10. var tarzan = {
  11. [[Prototype]]: PersonProto,
  12. name: 'Tarzan'
  13. };

The objects jane and tarzan are both considered “persons” and share the prototype object PersonProto.Let’s turn that prototype into a constructor Person that creates objects like jane and tarzan. The objects a constructor creates are called its instances. Such instances have the same structure as jane and tarzan, consisting of two parts:

  • Data is instance-specific and stored in the own properties of the instance objects (jane and tarzan in the preceding example).
  • Behavior is shared by all instances—they have a common prototype object with methods (PersonProto in the preceding example).

A constructor is a function that is invoked via the new operator. By convention, the names of constructors start with uppercase letters, while the names of normal functions and methods start with lowercase letters. The function itself sets up part 1:

  1. function Person(name) {
  2. this.name = name;
  3. }

The object in Person.prototype becomes the prototype of all instances of Person. It contributes part 2:

  1. Person.prototype.describe = function () {
  2. return 'Person named '+this.name;
  3. };

Let’s create and use an instance of Person:

  1. > var jane = new Person('Jane');
  2. > jane.describe()
  3. 'Person named Jane'

We can see that Person is a normal function. It only becomes a constructor when it is invoked via new. The new operator performs the following steps:

  • First the behavior is set up: a new object is created whose prototype is Person. prototype.
  • Then the data is set up: Person receives that object as the implicit parameter this and adds instance properties.

Figure 17-3 shows what the instance jane looks like. The property constructor of Person.prototype points back to the constructor and is explained in The constructor Property of Instances.

jane is an instance of the constructor Person; its prototype is the object Person.prototype.

Figure 17-3. jane is an instance of the constructor Person; its prototype is the object Person.prototype.

The instanceof operator allows us to check whether an object is an instance of a given constructor:

  1. > jane instanceof Person
  2. true
  3. > jane instanceof Date
  4. false

The new Operator Implemented in JavaScript

If you were to manually implement the new operator, it would look roughly as follows:

  1. function newOperator(Constr, args) {
  2. var thisValue = Object.create(Constr.prototype); // (1)
  3. var result = Constr.apply(thisValue, args);
  4. if (typeof result === 'object' && result !== null) {
  5. return result; // (2)
  6. }
  7. return thisValue;
  8. }

In line (1), you can see that the prototype of an instance created by a constructor Constr is Constr.prototype.

Line (2) reveals another feature of the new operator: you can return an arbitrary object from a constructor and it becomes the result of the new operator. This is useful if you want a constructor to return an instance of a subconstructor (an example is given in Returning arbitrary objects from a constructor).

Terminology: The Two Prototypes

Unfortunately, the term prototype is used ambiguously in JavaScript:

  • Prototype 1: The prototype relationship
  • An object can be the prototype of another object:
  1. > var proto = {};
  2. > var obj = Object.create(proto);
  3. > Object.getPrototypeOf(obj) === proto
  4. true

In the preceding example, proto is the prototype of obj.

  • Prototype 2: The value of the property prototype
  • Each constructor C has a prototype property that refers to an object. That object becomes the prototype of all instances of C:
  1. > function C() {}
  2. > Object.getPrototypeOf(new C()) === C.prototype
  3. true

Usually the context makes it clear which of the two prototypes is meant. Should disambiguation be necessary, then we are stuck with prototype to describe the relationship between objects, because that name has made it into the standard library via getPrototypeOf and isPrototypeOf. We thus need to find a different name for the object referenced by the prototype property. One possibility is constructor prototype, but that is problematic because constructors have prototypes, too:

  1. > function Foo() {}
  2. > Object.getPrototypeOf(Foo) === Function.prototype
  3. true

Thus, instance prototype is the best option.

The constructor Property of Instances

By default, each function C contains an instance prototype object C.prototype whose property constructor points back to C:

  1. > function C() {}
  2. > C.prototype.constructor === C
  3. true

Because the constructor property is inherited from the prototype by each instance, you can use it to get the constructor of an instance:

  1. > var o = new C();
  2. > o.constructor
  3. [Function: C]

Use cases for the constructor property

  • Switching over an object’s constructor
  • In the following catch clause, we take different actions, depending on the constructor of the caught exception:
  1. try {
  2. ...
  3. } catch (e) {
  4. switch (e.constructor) {
  5. case SyntaxError:
  6. ...
  7. break;
  8. case CustomError:
  9. ...
  10. break;
  11. ...
  12. }
  13. }

Warning

This approach detects only direct instances of a given constructor. In contrast, instanceof detects both direct instances and instances of all subconstructors.

  • Determining the name of an object’s constructor
  • For example:
  1. > function Foo() {}
  2. > var f = new Foo();
  3. > f.constructor.name
  4. 'Foo'

Warning

Not all JavaScript engines support the property name for functions.

  • Creating similar objects
  • This is how you create a new object, y, that has the same constructor as an existing object, x:
  1. function Constr() {}
  2. var x = new Constr();
  3.  
  4. var y = new x.constructor();
  5. console.log(y instanceof Constr); // true

This trick is handy for a method that must work for instances of subconstructors and wants to create a new instance that is similar to this. Then you can’t use a fixed constructor:

  1. SuperConstr.prototype.createCopy = function () {
  2. return new this.constructor(...);
  3. };
  • Referring to a superconstructor
  • Some inheritance libraries assign the superprototype to a property of a subconstructor. For example, the YUI framework provides subclassing via Y.extend:
  1. function Super() {
  2. }
  3. function Sub() {
  4. Sub.superclass.constructor.call(this); // (1)
  5. }
  6. Y.extend(Sub, Super);

The call in line (1) works, because extend sets Sub.superclass to Super.prototype. Thanks to the constructor property, you can call the superconstructor as a method.

Note

The instanceof operator (see The instanceof Operator) does not rely on the property constructor.

Best practice

Make sure that for each constructor C, the following assertion holds:

  1. C.prototype.constructor === C

By default, every function f already has a property prototype that is set up correctly:

  1. > function f() {}
  2. > f.prototype.constructor === f
  3. true

You should thus avoid replacing this object and only add properties to it:

  1. // Avoid:
  2. C.prototype = {
  3. method1: function (...) { ... },
  4. ...
  5. };
  6.  
  7. // Prefer:
  8. C.prototype.method1 = function (...) { ... };
  9. ...

If you do replace it, you should manually assign the correct value to constructor:

  1. C.prototype = {
  2. constructor: C,
  3. method1: function (...) { ... },
  4. ...
  5. };

Note that nothing crucial in JavaScript depends on the constructor property; but it is good style to set it up, because it enables the techniques mentioned in this section.

The instanceof Operator

The instanceof operator:

  1. value instanceof Constr

determines whether value has been created by the constructor Constr or a subconstructor. It does so by checking whether Constr.prototype is in the prototype chain of value. Therefore, the following two expressions are equivalent:

  1. value instanceof Constr
  2. Constr.prototype.isPrototypeOf(value)

Here are some examples:

  1. > {} instanceof Object
  2. true
  3.  
  4. > [] instanceof Array // constructor of []
  5. true
  6. > [] instanceof Object // super-constructor of []
  7. true
  8.  
  9. > new Date() instanceof Date
  10. true
  11. > new Date() instanceof Object
  12. true

As expected, instanceof is always false for primitive values:

  1. > 'abc' instanceof Object
  2. false
  3. > 123 instanceof Number
  4. false

Finally, instanceof throws an exception if its right side isn’t a function:

  1. > [] instanceof 123
  2. TypeError: Expecting a function in instanceof check

Pitfall: objects that are not instances of Object

Almost all objects are instances of Object, because Object.prototype is in their prototype chain. But there are also objects where that is not the case. Here are two examples:

  1. > Object.create(null) instanceof Object
  2. false
  3. > Object.prototype instanceof Object
  4. false

The former object is explained in more detail in The dict Pattern: Objects Without Prototypes Are Better Maps.The latter object is where most prototype chains end (and they must end somewhere).Neither object has a prototype:

  1. > Object.getPrototypeOf(Object.create(null))
  2. null
  3. > Object.getPrototypeOf(Object.prototype)
  4. null

But typeof correctly classifies them as objects:

  1. > typeof Object.create(null)
  2. 'object'
  3. > typeof Object.prototype
  4. 'object'

This pitfall is not a deal-breaker for most use cases for instanceof, but you have to be aware of it.

Pitfall: crossing realms (frames or windows)

In web browsers, each frame and window has its own realm with separate global variables. That prevents instanceof from working for objects that cross realms. To see why, look at the following code:

  1. if (myvar instanceof Array) ... // Doesn’t always work

If myvar is an array from a different realm, then its prototype is the Array.prototype from that realm. Therefore, instanceof will not find the Array.prototype of the current realm in the prototype chain of myvar and will return false. ECMAScript 5 has the function Array.isArray(), which always works:

  1. <head>
  2. <script>
  3. function test(arr) {
  4. var iframe = frames[0];
  5.  
  6. console.log(arr instanceof Array); // false
  7. console.log(arr instanceof iframe.Array); // true
  8. console.log(Array.isArray(arr)); // true
  9. }
  10. </script>
  11. </head>
  12. <body>
  13. <iframe srcdoc="<script>window.parent.test([])</script>">
  14. </iframe>
  15. </body>

Obviously, this is also an issue with non-built-in constructors.

Apart from using Array.isArray(), there are several things you can do to work around this problem:

  • Avoid objects crossing realms. Browsers have the postMessage() method, which can copy an object to another realm instead of passing a reference.
  • Check the name of the constructor of an instance (only works on engines that support the property name for functions):
  1. someValue.constructor.name === 'NameOfExpectedConstructor'
  • Use a prototype property to mark instances as belonging to a type T. There are several ways in which you can do so. The checks for whether value is an instance of T look as follows:
  • value.isT():The prototype of T instances must return true from this method; a common superconstructor should return the default value, false.
  • 'T' in value:You must tag the prototype of T instances with a property whose key is 'T' (or something more unique).
  • value.TYPE_NAME === 'T':Every relevant prototype must have a TYPE_NAME property with an appropriate value.

Tips for Implementing Constructors

This section gives a few tips for implementing constructors.

Protection against forgetting new: strict mode

If you forget new when you use a constructor, you are calling it as a function instead of as a constructor. In sloppy mode, you don’t get an instance and global variables are created. Unfortunately, all of this happens without a warning:

  1. function SloppyColor(name) {
  2. this.name = name;
  3. }
  4. var c = SloppyColor('green'); // no warning!
  5.  
  6. // No instance is created:
  7. console.log(c); // undefined
  8. // A global variable is created:
  9. console.log(name); // green

In strict mode, you get an exception:

  1. function StrictColor(name) {
  2. 'use strict';
  3. this.name = name;
  4. }
  5. var c = StrictColor('green');
  6. // TypeError: Cannot set property 'name' of undefined

Returning arbitrary objects from a constructor

In many object-oriented languages, constructors can produce only direct instances. For example, consider Java: let’s say you want to implement a class Expression that has the subclasses Addition and Multiplication. Parsing produces direct instances of the latter two classes. You can’t implement it as a constructor of Expression, because that constructor can produce only direct instances of Expression. As a workaround, static factory methods are used in Java:

  1. class Expression {
  2. // Static factory method:
  3. public static Expression parse(String str) {
  4. if (...) {
  5. return new Addition(...);
  6. } else if (...) {
  7. return new Multiplication(...);
  8. } else {
  9. throw new ExpressionException(...);
  10. }
  11. }
  12. }
  13. ...
  14. Expression expr = Expression.parse(someStr);

In JavaScript, you can simply return whatever object you need from a constructor. Thus, the JavaScript version of the preceding code would look like:

  1. function Expression(str) {
  2. if (...) {
  3. return new Addition(..);
  4. } else if (...) {
  5. return new Multiplication(...);
  6. } else {
  7. throw new ExpressionException(...);
  8. }
  9. }
  10. ...
  11. var expr = new Expression(someStr);

That is good news: JavaScript constructors don’t lock you in, so you can always change your mind as to whether a constructor should return a direct instance or something else.

Data in Prototype Properties

This section explains that in most cases, you should not put data in prototype properties. There are, however, a few exceptions to that rule.

Avoid Prototype Properties with Initial Values for Instance Properties

Prototypes contain properties that are shared by several objects. Hence, they work well for methods. Additionally, with a technique that is described next, you can also use them to provide initial values for instance properties. I’ll later explain why that is not recommended.

A constructor usually sets instance properties to initial values. If one such value is a default, then you don’t need to create an instance property. You only need a prototype property with the same key whose value is the default. For example:

  1. /**
  2. * Anti-pattern: don’t do this
  3. *
  4. * @param data an array with names
  5. */
  6. function Names(data) {
  7. if (data) {
  8. // There is a parameter
  9. // => create instance property
  10. this.data = data;
  11. }
  12. }
  13. Names.prototype.data = [];

The parameter data is optional. If it is missing, the instance does not get a property data, but inherits Names.prototype.data instead.

This approach mostly works: you can create an instance n of Names. Getting n.data reads Names.prototype.data. Setting n.data creates a new own property in n and preserves the shared default value in the prototype. We only have a problem if we change the default value (instead of replacing it with a new value):

  1. > var n1 = new Names();
  2. > var n2 = new Names();
  3.  
  4. > n1.data.push('jane'); // changes default value
  5. > n1.data
  6. [ 'jane' ]
  7.  
  8. > n2.data
  9. [ 'jane' ]

In the preceding example, push() changed the array in Names.prototype.data. Sincethat array is shared by all instances without an own property data,n2.data’s initial value has changed, too.

Best practice: don’t share default values

Given what we’ve just discussed, it is better to not share default values and to always createnew ones:

  1. function Names(data) {
  2. this.data = data || [];
  3. }

Obviously, the problem of modifying a shared default value does not arise if that value is immutable (as all primitives are; see Primitive Values). But for consistency’s sake, it’s best to stick to a single way of setting up properties. I also prefer to maintain the usual separation of concerns (see Layer 3: Constructors—Factories for Instances): the constructor sets up the instance properties, and the prototype contains the methods.

ECMAScript 6 will make this even more of a best practice, because constructor parameters can have default values and you can define prototype methods via classes, but not prototype properties with data.

Creating instance properties on demand

Occasionally, creating a property value is an expensive operation (computationally or storage-wise). In that case, you can create an instance property on demand:

  1. function Names(data) {
  2. if (data) this.data = data;
  3. }
  4. Names.prototype = {
  5. constructor: Names, // (1)
  6. get data() {
  7. // Define, don’t assign
  8. // => avoid calling the (nonexistent) setter
  9. Object.defineProperty(this, 'data', {
  10. value: [],
  11. enumerable: true,
  12. configurable: false,
  13. writable: false
  14. });
  15. return this.data;
  16. }
  17. };

We can’t add the property data to the instance via assignment, because JavaScript would complain about a missing setter (which it does when it only finds a getter). Therefore, we add it via Object.defineProperty(). Consult Properties: Definition Versus Assignment to review the differences between defining and assigning.In line (1), we are ensuring that the property constructor is set up properly (see The constructor Property of Instances).

Obviously, that is quite a bit of work, so you have to be sure it is worth it.

Avoid Nonpolymorphic Prototype Properties

If the same property (same key, same semantics, generally different values), exists in several prototypes, it is called polymorphic. Then the result of reading the property via an instance is dynamically determined via that instance’s prototype. Prototype properties that are not used polymorphically can be replaced by variables (which better reflects their nonpolymorphic nature).

For example, you can store a constant in a prototype property and access it via this:

  1. function Foo() {}
  2. Foo.prototype.FACTOR = 42;
  3. Foo.prototype.compute = function (x) {
  4. return x * this.FACTOR;
  5. };

This constant is not polymorphic. Therefore, you can just as well access it via a variable:

  1. // This code should be inside an IIFE or a module
  2. function Foo() {}
  3. var FACTOR = 42;
  4. Foo.prototype.compute = function (x) {
  5. return x * FACTOR;
  6. };

Polymorphic Prototype Properties

Here is an example of polymorphic prototype properties with immutable data. Tagging instances of a constructor via prototype properties enables you to tell them apart from instances of a different constructor:

  1. function ConstrA() { }
  2. ConstrA.prototype.TYPE_NAME = 'ConstrA';
  3.  
  4. function ConstrB() { }
  5. ConstrB.prototype.TYPE_NAME = 'ConstrB';

Thanks to the polymorphic “tag” TYPE_NAME, you can distinguish the instances of ConstrA and ConstrB even when they cross realms (then instanceof does not work; see Pitfall: crossing realms (frames or windows)).

Keeping Data Private

JavaScript does not have dedicated means for managing private data for an object. This section will describe three techniques for working around that limitation:

  • Private data in the environment of a constructor
  • Private data in properties with marked keys
  • Private data in properties with reified keys

Additionally, I will explain how to keep global data private via IIFEs.

Private Data in the Environment of a Constructor (Crockford Privacy Pattern)

When a constructor is invoked, two things are created: the constructor’s instance and an environment (see Environments: Managing Variables). The instance is to be initialized by the constructor. The environment holds the constructor’s parameters and local variables. Every function (which includes methods) created inside the constructor will retain a reference to the environment—the environment in which it was created. Thanks to that reference, it will always have access to the environment, even after the constructor is finished. This combination of function and environment is called a closure (Closures: Functions Stay Connected to Their Birth Scopes). The constructor’s environment is thus data storage that is independent of the instance and related to it only because the two are created at the same time. To properly connect them, we must have functions that live in both worlds. Using Douglas Crockford’s terminology, an instance can have three kinds of values associated with it (see Figure 17-4):

  • Public properties
  • Values stored in properties (either in the instance or in its prototype) are publicly accessible.
  • Private values
  • Data and functions stored in the environment are private—only accessible to the constructor and to the functions it created.
  • Privileged methods
  • Private functions can access public properties, but public methods in the prototype can’t access private data. We thus need privileged methods—public methods in the instance.Privileged methods are public and can be called by everyone, but they also have access to private values, because they were created in the constructor.

When a constructor Constr is invoked, two data structures are created: an environment for parameters and local variables and an instance to be initialized.

Figure 17-4. When a constructor Constr is invoked, two data structures are created: an environment for parameters and local variables and an instance to be initialized.

The following sections explain each kind of value in more detail.

Public properties

Remember that given a constructor Constr, there are two kinds of properties that are public, accessible to everyone. First, prototype properties are stored in Constr.prototype and shared by all instances. Prototype properties are usually methods:

  1. Constr.prototype.publicMethod = ...;

Second, instance properties are unique to each instance. They are added in the constructor and usually hold data (not methods):

  1. function Constr(...) {
  2. this.publicData = ...;
  3. ...
  4. }

Private values

The constructor’s environment consists of the parameters and local variables. They are accessible only from inside the constructor and thus private to the instance:

  1. function Constr(...) {
  2. ...
  3. var that = this; // make accessible to private functions
  4.  
  5. var privateData = ...;
  6.  
  7. function privateFunction(...) {
  8. // Access everything
  9. privateData = ...;
  10.  
  11. that.publicData = ...;
  12. that.publicMethod(...);
  13. }
  14. ...
  15. }

Privileged methods

Private data is so safe from outside access that prototype methods can’t access it. But then how else would you use it after leaving the constructor? The answer is privileged methods: functions created in the constructor are added as instance methods. That means that, on one hand, they can access private data; on the other hand, they are public and therefore seen by prototype methods. In other words, they serve as mediators between private data and the public (including prototype methods):

  1. function Constr(...) {
  2. ...
  3. this.privilegedMethod = function (...) {
  4. // Access everything
  5. privateData = ...;
  6. privateFunction(...);
  7.  
  8. this.publicData = ...;
  9. this.publicMethod(...);
  10. };
  11. }

An example

The following is an implementation of a StringBuilder, using the Crockford privacy pattern:

  1. function StringBuilder() {
  2. var buffer = [];
  3. this.add = function (str) {
  4. buffer.push(str);
  5. };
  6. this.toString = function () {
  7. return buffer.join('');
  8. };
  9. }
  10. // Can’t put methods in the prototype!

Here is the interaction:

  1. > var sb = new StringBuilder();
  2. > sb.add('Hello');
  3. > sb.add(' world!');
  4. > sb.toString()
  5. Hello world!’

The pros and cons of the Crockford privacy pattern

Here are some points to consider when you are using the Crockford privacy pattern:

  • It’s not very elegant
  • Mediating access to private data via privileged methods introduces an unnecessary indirection. Privileged methods and private functions both destroy the separation of concerns between the constructor (setting up instance data) and the instance prototype (methods).
  • It’s completely secure
  • There is no way to access the environment’s data from outside, which makes this solution secure if you need that (e.g., for security-critical code). On the other hand, private data not being accessible to the outside can also be an inconvenience. Sometimes you want to unit-test private functionality. And some temporary quick fixes depend on the ability to access private data. This kind of quick fix cannot be predicted, so no matter how good your design is, the need can arise.
  • It may be slower
  • Accessing properties in the prototype chain is highly optimized in current JavaScript engines. Accessing values in the closure may be slower. But these things change constantly, so you’ll have to measure should this really matter for your code.
  • It consumes more memory
  • Keeping the environment around and putting privileged methods in instances costs memory. Again, be sure it really matters for your code and measure.

Private Data in Properties with Marked Keys

For most non-security-critical applications, privacy is more like a hint to clients of an API: “You don’t need to see this.” That’s the key benefit of encapsulation—hiding complexity. Even though more is going on under the hood, you only need to understand the public part of an API. The idea of a naming convention is to let clients know about privacy by marking the key of a property. A prefixed underscore is often used for this purpose.

Let’s rewrite the previous StringBuilder example so that the buffer is kept in a property _buffer, which is private, but by convention only:

  1. function StringBuilder() {
  2. this._buffer = [];
  3. }
  4. StringBuilder.prototype = {
  5. constructor: StringBuilder,
  6. add: function (str) {
  7. this._buffer.push(str);
  8. },
  9. toString: function () {
  10. return this._buffer.join('');
  11. }
  12. };

Here are some pros and cons of privacy via marked property keys:

  • It offers a more natural coding style
  • Being able to access private and public data in the same manner is more elegant than using environments for privacy.
  • It pollutes the namespace of properties
  • Properties with marked keys can be seen everywhere. The more people use IDEs, the more it will be a nuisance that they are shown alongside public properties, in places where they should be hidden. IDEs could, in theory, adapt by recognizing naming conventions and by hiding private properties where possible.
  • Private properties can be accessed from “outside”
  • That can be useful for unit tests and quick fixes. Additionally, subconstructors and helper functions (so-called “friend functions”) can profit from easier access to private data. The environment approach doesn’t offer this kind of flexibility; private data can be accessed only from within the constructor.
  • It can lead to key clashes
  • Keys of private properties can clash. This is already an issue for subconstructors, but it is even more problematic if you work with multiple inheritance (as enabled by some libraries). With the environment approach, there are never any clashes.

Private Data in Properties with Reified Keys

One problem with a naming convention for private properties is that keys might clash (e.g., a key from a constructor with a key from a subconstructor, or a key from a mixin with a key from a constructor). You can make such clashes less likely by using longer keys, that, for example, include the name of the constructor. Then, in the previous case, the private property buffer would be called _StringBuilder_buffer. If such a key is too long for your taste, you have the option of _reifying it, of storing it in a variable:

  1. var KEY_BUFFER = '_StringBuilder_buffer';

We now access the private data via this[KEY_BUFFER]:

  1. var StringBuilder = function () {
  2. var KEY_BUFFER = '_StringBuilder_buffer';
  3.  
  4. function StringBuilder() {
  5. this[KEY_BUFFER] = [];
  6. }
  7. StringBuilder.prototype = {
  8. constructor: StringBuilder,
  9. add: function (str) {
  10. this[KEY_BUFFER].push(str);
  11. },
  12. toString: function () {
  13. return this[KEY_BUFFER].join('');
  14. }
  15. };
  16. return StringBuilder;
  17. }();

We have wrapped an IIFE around StringBuilder so that the constant KEY_BUFFER stays local and doesn’t pollute the global namespace.

Reified property keys enable you to use UUIDs (universally unique identifiers) in keys. For example, via Robert Kieffer’s node-uuid:

  1. var KEY_BUFFER = '_StringBuilder_buffer_' + uuid.v4();

KEY_BUFFER has a different value each time the code runs. It may, for example, look like this:

  1. _StringBuilder_buffer_110ec58a-a0f2-4ac4-8393-c866d813b8d1

Long keys with UUIDs make key clashes virtually impossible.

Keeping Global Data Private via IIFEs

This subsection explains how to keep global data private to singleton objects, constructors, and methods, via IIFEs (see Introducing a New Scope via an IIFE). Those IIFEs create new environments (refer back to Environments: Managing Variables), which is where you put the private data.

Attaching private global data to a singleton object

You don’t need a constructor to associate an object with private data in an environment. The following example shows how to use an IIFE for the same purpose, by wrapping it around a singleton object:

  1. var obj = function () { // open IIFE
  2.  
  3. // public
  4. var self = {
  5. publicMethod: function (...) {
  6. privateData = ...;
  7. privateFunction(...);
  8. },
  9. publicData: ...
  10. };
  11.  
  12. // private
  13. var privateData = ...;
  14. function privateFunction(...) {
  15. privateData = ...;
  16. self.publicData = ...;
  17. self.publicMethod(...);
  18. }
  19.  
  20. return self;
  21. }(); // close IIFE

Keeping global data private to all of a constructor

Some global data is relevant only for a constructor and the prototype methods. By wrapping an IIFE around both, you can hide it from public view. Private Data in Properties with Reified Keys gave an example: the constructor StringBuilder and its prototype methods use the constant KEY_BUFFER, which contains a property key. That constant is stored in the environment of an IIFE:

  1. var StringBuilder = function () { // open IIFE
  2. var KEY_BUFFER = '_StringBuilder_buffer_' + uuid.v4();
  3.  
  4. function StringBuilder() {
  5. this[KEY_BUFFER] = [];
  6. }
  7. StringBuilder.prototype = {
  8. // Omitted: methods accessing this[KEY_BUFFER]
  9. };
  10. return StringBuilder;
  11. }(); // close IIFE

Note that if you are using a module system (see Chapter 31), you can achieve the same effect with cleaner code by putting the constructor plus methods in a module.

Attaching global data to a method

Sometimes you only need global data for a single method. You can keep it private by putting it in the environment of an IIFE that you wrap around the method. For example:

  1. var obj = {
  2. method: function () { // open IIFE
  3.  
  4. // method-private data
  5. var invocCount = 0;
  6.  
  7. return function () {
  8. invocCount++;
  9. console.log('Invocation #'+invocCount);
  10. return 'result';
  11. };
  12. }() // close IIFE
  13. };

Here is the interaction:

  1. > obj.method()
  2. Invocation #1
  3. 'result'
  4. > obj.method()
  5. Invocation #2
  6. 'result'

Layer 4: Inheritance Between Constructors

In this section, we examine how constructors can be inherited from: given a constructor Super, how can we write a new constructor, Sub, that has all the features of Super plus some features of its own? Unfortunately, JavaScript does not have a built-in mechanism for performing this task. Hence, we’ll have to do some manual work.

Figure 17-5 illustrates the idea: the subconstructor Sub should have all of the properties of Super (both prototype properties and instance properties) in addition to its own. Thus, we have a rough idea of what Sub should look like, but don’t know how to get there. There are several things we need to figure out, which I’ll explain next:

  • Inheriting instance properties.
  • Inheriting prototype properties.
  • Ensuring that instanceof works: if sub is an instance of Sub, we also want sub instanceof Super to be true.
  • Overriding a method to adapt one of Super’s methods in Sub.
  • Making supercalls: if we have overridden one of Super’s methods, we may need to call the original method from Sub.

Sub should inherit from Super: it should have all of Super’s prototype properties and all of Super’s instance properties in addition to its own. Note that methodB overrides Super’s methodB.

Figure 17-5. Sub should inherit from Super: it should have all of Super’s prototype properties and all of Super’s instance properties in addition to its own. Note that methodB overrides Super’s methodB.

Inheriting Instance Properties

Instance properties are set up in the constructor itself, so inheriting the superconstructor’s instance properties involves calling that constructor:

  1. function Sub(prop1, prop2, prop3, prop4) {
  2. Super.call(this, prop1, prop2); // (1)
  3. this.prop3 = prop3; // (2)
  4. this.prop4 = prop4; // (3)
  5. }

When Sub is invoked via new, its implicit parameter this refers to a fresh instance. It first passes that instance on to Super (1), which adds its instance properties. Afterward, Sub sets up its own instance properties (2,3). The trick is not to invoke Super via new, because that would create a fresh superinstance. Instead, we call Super as a function and hand in the current (sub)instance as the value of this.

Inheriting Prototype Properties

Shared properties such as methods are kept in the instance prototype. Thus, we need to find a way for Sub.prototype to inherit all of Super.prototype’s properties. The solution is to give Sub.prototype the prototype Super.prototype.

Confused by the two kinds of prototypes?

Yes, JavaScript terminology is confusing here. If you feel lost, consult Terminology: The Two Prototypes, which explains how they differ.

This is the code that achieves that:

  1. Sub.prototype = Object.create(Super.prototype);
  2. Sub.prototype.constructor = Sub;
  3. Sub.prototype.methodB = ...;
  4. Sub.prototype.methodC = ...;

Object.create() produces a fresh object whose prototype is Super.prototype. Afterward, we add Sub’s methods. As explained in The constructor Property of Instances, we also need to set up the property constructor, because we have replaced the original instance prototype where it had the correct value.

Figure 17-6 shows how Sub and Super are related now. Sub’s structure does resemble what I have sketched in Figure 17-5. The diagram does not show the instance properties, which are set up by the function call mentioned in the diagram.

The constructor Sub inherits the constructor Super by calling it and by making Sub.prototype a prototypee of Super.prototype.

Figure 17-6. The constructor Sub inherits the constructor Super by calling it and by making Sub.prototype a prototypee of Super.prototype.

Ensuring That instanceof Works

“Ensuring that instanceof works” means that every instance of Sub must also be an instance of Super. Figure 17-7 shows what the prototype chain of subInstance, an instance of Sub, looks like: its first prototype is Sub.prototype, and its second prototype is Super.prototype.

subInstance has been created by the constructor Sub. It has the two prototypes Sub.prototype and Super.prototype.

Figure 17-7. subInstance has been created by the constructor Sub. It has the two prototypes Sub.prototype and Super.prototype.

Let’s start with an easier question: is subInstance an instance of Sub? Yes, it is, because the following two assertions are equivalent (the latter can be considered the definition of the former):

  1. subInstance instanceof Sub
  2. Sub.prototype.isPrototypeOf(subInstance)

As mentioned before, Sub.prototype is one of the prototypes of subInstance, so both assertions are true. Similarly, subInstance is also an instance of Super, because the following two assertions hold:

  1. subInstance instanceof Super
  2. Super.prototype.isPrototypeOf(subInstance)

Overriding a Method

We override a method in Super.prototype by adding a method with the same name to Sub.prototype. methodB is an example and in Figure 17-7, we can see why it works: the search for methodB begins in subInstance and finds Sub.prototype.methodB before Super.prototype.methodB.

Making a Supercall

To understand supercalls, you need to know the term home object. The home object of a method is the object that owns the property whose value is the method. For example, the home object of Sub.prototype.methodB is Sub.prototype. Supercalling a method foo involves three steps:

  • Start your search “after” (in the prototype of) the home object of the current method.
  • Look for a method whose name is foo.
  • Invoke that method with the current this. The rationale is that the supermethod must work with the same instance as the current method; it must be able to access the same instance properties.

Therefore, the code of the submethod looks as follows. It supercalls itself, it calls the method it has overridden:

  1. Sub.prototype.methodB = function (x, y) {
  2. var superResult = Super.prototype.methodB.call(this, x, y); // (1)
  3. return this.prop3 + ' ' + superResult;
  4. }

One way of reading the supercall at (1) is as follows: refer to the supermethod directly and call it with the current this. However, if we split it into three parts, we find the aforementioned steps:

  • Super.prototype: Start your search in Super.prototype, the prototype of Sub.prototype (the home object of the current method Sub.prototype.methodB).
  • methodB: Look for a method with the name methodB.
  • call(this, …): Call the method found in the previous step, and maintain the current this.

Avoiding Hardcoding the Name of the Superconstructor

Until now, we have always referred to supermethods and superconstructors by mentioning the superconstructor name. This kind of hardcoding makes your code less flexible. You can avoid it by assigning the superprototype to a property of Sub:

  1. Sub._super = Super.prototype;

Then calling the superconstructor and a supermethod looks as follows:

  1. function Sub(prop1, prop2, prop3, prop4) {
  2. Sub._super.constructor.call(this, prop1, prop2);
  3. this.prop3 = prop3;
  4. this.prop4 = prop4;
  5. }
  6. Sub.prototype.methodB = function (x, y) {
  7. var superResult = Sub._super.methodB.call(this, x, y);
  8. return this.prop3 + ' ' + superResult;
  9. }

Setting up Sub._super is usually handled by a utility function that also connects the subprototype to the superprototype. For example:

  1. function subclasses(SubC, SuperC) {
  2. var subProto = Object.create(SuperC.prototype);
  3. // Save `constructor` and, possibly, other methods
  4. copyOwnPropertiesFrom(subProto, SubC.prototype);
  5. SubC.prototype = subProto;
  6. SubC._super = SuperC.prototype;
  7. };

This code uses the helper function copyOwnPropertiesFrom(), which is shown and explained in Copying an Object.

Tip

Read “subclasses” as a verb: SubC subclasses SuperC.Such a utility function can take some of the pain out of creating a subconstructor: there are fewer things to do manually, and the name of the superconstructor is never mentioned redundantly. The following example demonstrates how it simplifies code.

Example: Constructor Inheritance in Use

As a concrete example, let’s assume that the constructor Person already exists:

  1. function Person(name) {
  2. this.name = name;
  3. }
  4. Person.prototype.describe = function () {
  5. return 'Person called '+this.name;
  6. };

We now want to create the constructor Employee as a subconstructor of Person. We do so manually, which looks like this:

  1. function Employee(name, title) {
  2. Person.call(this, name);
  3. this.title = title;
  4. }
  5. Employee.prototype = Object.create(Person.prototype);
  6. Employee.prototype.constructor = Employee;
  7. Employee.prototype.describe = function () {
  8. return Person.prototype.describe.call(this)+' ('+this.title+')';
  9. };

Here is the interaction:

  1. > var jane = new Employee('Jane', 'CTO');
  2. > jane.describe()
  3. Person called Jane (CTO)
  4. > jane instanceof Employee
  5. true
  6. > jane instanceof Person
  7. true

The utility function subclasses() from the previous section makes the code of Employee slightly simpler and avoids hardcoding the superconstructor Person:

  1. function Employee(name, title) {
  2. Employee._super.constructor.call(this, name);
  3. this.title = title;
  4. }
  5. Employee.prototype.describe = function () {
  6. return Employee._super.describe.call(this)+' ('+this.title+')';
  7. };
  8. subclasses(Employee, Person);

Example: The Inheritance Hierarchy of Built-in Constructors

Built-in constructors use the same subclassing approach described in this section. For example, Array is a subconstructor of Object. Therefore, the prototype chain of an instance of Array looks like this:

  1. > var p = Object.getPrototypeOf
  2.  
  3. > p([]) === Array.prototype
  4. true
  5. > p(p([])) === Object.prototype
  6. true
  7. > p(p(p([]))) === null
  8. true

Antipattern: The Prototype Is an Instance of the Superconstructor

Before ECMAScript 5 and Object.create(), an often-used solution was to create the subprototype by invoking the superconstructor:

  1. Sub.prototype = new Super(); // Don’t do this

This is not recommended under ECMAScript 5. The prototype will have all of Super’s instance properties, which it has no use for. Therefore, it is better to use the aforementioned pattern (involving Object.create()).

Methods of All Objects

Almost all objects have Object.prototype in their prototype chain:

  1. > Object.prototype.isPrototypeOf({})
  2. true
  3. > Object.prototype.isPrototypeOf([])
  4. true
  5. > Object.prototype.isPrototypeOf(/xyz/)
  6. true

The following subsections describe the methods that Object.prototype provides for its prototypees.

Conversion to Primitive

The following two methods are used to convert an object to a primitive value:

  • Object.prototype.toString()
  • Returns a string representation of an object:
  1. > ({ first: 'John', last: 'Doe' }.toString())
  2. '[object Object]'
  3. > [ 'a', 'b', 'c' ].toString()
  4. 'a,b,c'
  • Object.prototype.valueOf()
  • This is the preferred way of converting an object to a number. The default implementation returns this:
  1. > var obj = {};
  2. > obj.valueOf() === obj
  3. true

valueOf is overridden by wrapper constructors to return the wrapped primitive:

  1. > new Number(7).valueOf()
  2. 7

The conversion to number and string (whether implicit or explicit) builds on the conversion to primitive (for details, see Algorithm: ToPrimitive()—Converting a Value to a Primitive). That is why you can use the aforementioned two methods to configure those conversions. valueOf() is preferred by the conversion to number:

  1. > 3 * { valueOf: function () { return 5 } }
  2. 15

toString() is preferred by the conversion to string:

  1. > String({ toString: function () { return 'ME' } })
  2. 'Result: ME'

The conversion to boolean is not configurable; objects are always considered to be true (see Converting to Boolean).

Object.prototype.toLocaleString()

This method returns a locale-specific string representation of an object. The default implementation calls toString(). Most engines don’t go beyond this support for this method. However, the ECMAScript Internationalization API (see The ECMAScript Internationalization API), which is supported by many modern engines, overrides it for several built-in constructors.

Prototypal Inheritance and Properties

The following methods help with prototypal inheritance and properties:

  • Object.prototype.isPrototypeOf(obj)
  • Returns true if the receiver is part of the prototype chain of obj:
  1. > var proto = { };
  2. > var obj = Object.create(proto);
  3. > proto.isPrototypeOf(obj)
  4. true
  5. > obj.isPrototypeOf(obj)
  6. false
  • Object.prototype.hasOwnProperty(key)
  • Returns true if this owns a property whose key is key. “Own” means that the property exists in the object itself and not in one of its prototypes.

Warning

You normally should invoke this method generically (not directly), especially on objects whose properties you don’t know statically. Why and how is explained in Iteration and Detection of Properties:

  1. > var proto = { foo: 'abc' };
  2. > var obj = Object.create(proto);
  3. > obj.bar = 'def';
  4.  
  5. > Object.prototype.hasOwnProperty.call(obj, 'foo')
  6. false
  7. > Object.prototype.hasOwnProperty.call(obj, 'bar')
  8. true
  • Object.prototype.propertyIsEnumerable(propKey)
  • Returns true if the receiver has a property with the key propKey that is enumerable and false otherwise:
  1. > var obj = { foo: 'abc' };
  2. > obj.propertyIsEnumerable('foo')
  3. true
  4. > obj.propertyIsEnumerable('toString')
  5. false
  6. > obj.propertyIsEnumerable('unknown')
  7. false

Generic Methods: Borrowing Methods from Prototypes

Sometimes instance prototypes have methods that are useful for more objects than those that inherit from them. This section explains how to use the methods of a prototype without inheriting from it.For example, the instance prototype Wine.prototype has the method incAge():

  1. function Wine(age) {
  2. this.age = age;
  3. }
  4. Wine.prototype.incAge = function (years) {
  5. this.age += years;
  6. }

The interaction is as follows:

  1. > var chablis = new Wine(3);
  2. > chablis.incAge(1);
  3. > chablis.age
  4. 4

The method incAge() works for any object that has the property age. How can we invoke it on an object that is not an instance of Wine? Let’s look at the preceding method call:

  1. chablis.incAge(1)

There are actually two arguments:

  • chablis is the receiver of the method call, passed to incAge via this.
  • 1 is an argument, passed to incAge via years.

We can’t replace the former with an arbitrary object—the receiver must be an instance of Wine. Otherwise, the method incAge is not found. But the preceding method call is equivalent to (refer back to Calling Functions While Setting this: call(), apply(), and bind()):

  1. Wine.prototype.incAge.call(chablis, 1)

With the preceding pattern, we can make an object the receiver (first argument of call) that is not an instance of Wine, because the receiver isn’t used to find the method Wine.prototype.incAge. In the following example, we apply the method incAge() to the object john:

  1. > var john = { age: 51 };
  2. > Wine.prototype.incAge.call(john, 3)
  3. > john.age
  4. 54

A function that can be used in this manner is called a generic method; it must be prepared for this not being an instance of “its” constructor. Thus, not all methods are generic; the ECMAScript language specification explicitly states which ones are (see A List of All Generic Methods).

Accessing Object.prototype and Array.prototype via Literals

Calling a method generically is quite verbose:

  1. Object.prototype.hasOwnProperty.call(obj, 'propKey')

You can make this shorter by accessing hasOwnProperty via an instance of Object, as created by an empty object literal {}:

  1. {}.hasOwnProperty.call(obj, 'propKey')

Similarly, the following two expressions are equivalent:

  1. Array.prototype.join.call(str, '-')
  2. [].join.call(str, '-')

The advantage of this pattern is that it is less verbose. But it is also less self-explanatory. Performance should not be an issue (at least long term), as engines can statically determine that the literals should not create objects.

Examples of Calling Methods Generically

These are a few examples of generic methods in use:

  1. > var arr1 = [ 'a', 'b' ];
  2. > var arr2 = [ 'c', 'd' ];
  3.  
  4. > [].push.apply(arr1, arr2)
  5. 4
  6. > arr1
  7. [ 'a', 'b', 'c', 'd' ]

This example is about turning an array into arguments, not about borrowing a method from another constructor.

  • Apply the array method join() to a string (which is not an array):
  1. > Array.prototype.join.call('abc', '-')
  2. 'a-b-c'
  • Apply the array method map() to a string:[17]
  1. > [].map.call('abc', function (x) { return x.toUpperCase() })
  2. [ 'A', 'B', 'C' ]

Using map() generically is more efficient than using split(''), which creates an intermediate array:

  1. > 'abc'.split('').map(function (x) { return x.toUpperCase() })
  2. [ 'A', 'B', 'C' ]
  • Apply a string method to nonstrings. toUpperCase() converts the receiver to a string and uppercases the result:
  1. > String.prototype.toUpperCase.call(true)
  2. 'TRUE'
  3. > String.prototype.toUpperCase.call(['a','b','c'])
  4. 'A,B,C'

Using generic array methods on plain objects gives you insight into how they work:

  • Invoke an array method on a fake array:
  1. > var fakeArray = { 0: 'a', 1: 'b', length: 2 };
  2. > Array.prototype.join.call(fakeArray, '-')
  3. 'a-b'
  • See how an array method transforms an object that it treats like an array:
  1. > var obj = {};
  2. > Array.prototype.push.call(obj, 'hello');
  3. 1
  4. > obj
  5. { '0': 'hello', length: 1 }

Array-Like Objects and Generic Methods

There are some objects in JavaScript that feel like an array, but actually aren’t. That means that while they have indexed access and a length property, they don’t have any of the array methods (forEach(), push, concat(), etc.). This is unfortunate, but as we will see, generic array methods enable a workaround. Examples of array-like objects include:

  1. > function args() { return arguments }
  2. > var arrayLike = args('a', 'b');
  3.  
  4. > arrayLike[0]
  5. 'a'
  6. > arrayLike.length
  7. 2

But none of the array methods are available:

  1. > arrayLike.join('-')
  2. TypeError: object has no method 'join'

That’s because arrayLike is not an instance of Array (and Array.prototype is not in the prototype chain):

  1. > arrayLike instanceof Array
  2. false
  • Browser DOM node lists, which are returned by document.getElementsBy*() (e.g., getElementsByTagName()), document.forms, and so on:
  1. > var elts = document.getElementsByTagName('h3');
  2. > elts.length
  3. 3
  4. > elts instanceof Array
  5. false
  • Strings, which are array-like, too:
  1. > 'abc'[1]
  2. 'b'
  3. > 'abc'.length
  4. 3

The term array-like can also be seen as a contract between generic array methods and objects. The objects have to fulfill certain requirements; otherwise, the methods won’t work on them. The requirements are:

  • The elements of an array-like object must be accessible via square brackets and integer indices starting at 0. All methods need read access, and some methods additionally need write access. Note that all objects support this kind of indexing: an index in brackets is converted to a string and used as a key to look up a property value:
  1. > var obj = { '0': 'abc' };
  2. > obj[0]
  3. 'abc'
  • An array-like object must have a length property whose value is the number of its elements. Some methods require length to be mutable (for example, reverse()). Values whose lengths are immutable (for example, strings) cannot be used with those methods.

Patterns for working with array-like objects

The following patterns are useful for working with array-like objects:

  • Turn an array-like object into an array:
  1. var arr = Array.prototype.slice.call(arguments);

The method slice() (see Concatenating, Slicing, Joining (Nondestructive)) without any arguments creates a copy of an array-like receiver:

  1. var copy = [ 'a', 'b' ].slice();
  • To iterate over all elements of an array-like object, you can use a simple for loop:
  1. function logArgs() {
  2. for (var i=0; i<arguments.length; i++) {
  3. console.log(i+'. '+arguments[i]);
  4. }
  5. }

But you can also borrow Array.prototype.forEach():

  1. function logArgs() {
  2. Array.prototype.forEach.call(arguments, function (elem, i) {
  3. console.log(i+'. '+elem);
  4. });
  5. }

In both cases, the interaction looks as follows:

  1. > logArgs('hello', 'world');
  2. 0. hello
  3. 1. world

A List of All Generic Methods

The following list includes all methods that are generic, as mentioned in the ECMAScript language specification:

  • concat
  • every
  • filter
  • forEach
  • indexOf
  • join
  • lastIndexOf
  • map
  • pop
  • push
  • reduce
  • reduceRight
  • reverse
  • shift
  • slice
  • some
  • sort
  • splice
  • toLocaleString
  • toString
  • unshift
  • toJSON
  • (All Object methods are automatically generic—they have to work for all objects.)
  • charAt
  • charCodeAt
  • concat
  • indexOf
  • lastIndexOf
  • localeCompare
  • match
  • replace
  • search
  • slice
  • split
  • substring
  • toLocaleLowerCase
  • toLocaleUpperCase
  • toLowerCase
  • toUpperCase
  • trim

Pitfalls: Using an Object as a Map

Since JavaScript has no built-in data structure for maps, objects are often used as maps from strings to values. Alas, that is more error-prone than it seems. This section explains three pitfalls that are involved in this task.

Pitfall 1: Inheritance Affects Reading Properties

The operations that read properties can be partitioned into two kinds:

  • Some operations consider the whole prototype chain and see inherited properties.
  • Other operations access only the own (noninherited) properties of an object.

You need to choose carefully between these kinds of operations when you read the entries of an object-as-map. To see why, consider the following example:

  1. var proto = { protoProp: 'a' };
  2. var obj = Object.create(proto);
  3. obj.ownProp = 'b';

obj is an object with one own property whose prototype is proto, which also has one own property. proto has the prototype Object.prototype, like all objects that are created by object literals. Thus, obj inherits properties from both proto and Object. prototype.

We want obj to be interpreted as a map with the single entry:

  1. ownProp: 'b'

That is, we want to ignore inherited properties and only consider own properties. Let’s see which read operations interpret obj in this manner and which don’t. Note that for objects-as-maps, we normally want to use arbitrary property keys, stored in variables. That rules out dot notation.

Checking whether a property exists

The in operator checks whether an object has a property with a given key, but it considers inherited properties:

  1. > 'ownProp' in obj // ok
  2. true
  3. > 'unknown' in obj // ok
  4. false
  5. > 'toString' in obj // wrong, inherited from Object.prototype
  6. true
  7. > 'protoProp' in obj // wrong, inherited from proto
  8. true

We need the check to ignore inherited properties. hasOwnProperty() does what we want:

  1. > obj.hasOwnProperty('ownProp') // ok
  2. true
  3. > obj.hasOwnProperty('unknown') // ok
  4. false
  5. > obj.hasOwnProperty('toString') // ok
  6. false
  7. > obj.hasOwnProperty('protoProp') // ok
  8. false

Collecting property keys

What operations can we use to find all of the keys of obj, while honoring our interpretation of it as a map? for-in looks like it might work. But, alas, it doesn’t:

  1. > for (propKey in obj) console.log(propKey)
  2. ownProp
  3. protoProp

It considers inherited enumerable properties. The reason that no properties of Object.prototype show up here is that all of them are nonenumerable.

In contrast, Object.keys() lists only own properties:

  1. > Object.keys(obj)
  2. [ 'ownProp' ]

This method returns only enumerable own properties; ownProp has been added via assignment and is thus enumerable by default. If you want to list all own properties, you need to use Object.getOwnPropertyNames().

Getting a property value

For reading the value of a property, we can only choose between the dot operator and the bracket operator. We can’t use the former, because we have arbitrary keys, stored in variables. That leaves us with the bracket operator, which considers inherited properties:

  1. > obj['toString']
  2. [Function: toString]

This is not what we want.There is no built-in operation for reading only own properties, but you can easily implement one yourself:

  1. function getOwnProperty(obj, propKey) {
  2. // Using hasOwnProperty() in this manner is problematic
  3. // (explained and fixed later)
  4. return (obj.hasOwnProperty(propKey)
  5. ? obj[propKey] : undefined);
  6. }

With that function, the inherited property toString is ignored:

  1. > getOwnProperty(obj, 'toString')
  2. undefined

Pitfall 2: Overriding Affects Invoking Methods

The function getOwnProperty() invoked the method hasOwnProperty() on obj. Normally, that is fine:

  1. > getOwnProperty({ foo: 123 }, 'foo')
  2. 123

However, if you add a property to obj whose key is hasOwnProperty, then that property overrides the method Object.prototype.hasOwnProperty() and getOwnProperty() ceases to work:

  1. > getOwnProperty({ hasOwnProperty: 123 }, 'foo')
  2. TypeError: Property 'hasOwnProperty' is not a function

You can fix this problem by directly referring to hasOwnProperty(). This avoids going through obj to find it:

  1. function getOwnProperty(obj, propKey) {
  2. return (Object.prototype.hasOwnProperty.call(obj, propKey)
  3. ? obj[propKey] : undefined);
  4. }

We have called hasOwnProperty() generically (see Generic Methods: Borrowing Methods from Prototypes).

Pitfall 3: The Special Property proto

In many JavaScript engines, the property proto (see The Special Property proto) is special: getting it retrieves the prototype of an object, and setting it changes the prototype of an object. This is why the object can’t store map data in a property whose key is 'proto'. If you want to allow the map key 'proto', you must escape it before using it as a property key:

  1. function get(obj, key) {
  2. return obj[escapeKey(key)];
  3. }
  4. function set(obj, key, value) {
  5. obj[escapeKey(key)] = value;
  6. }
  7. // Similar: checking if key exists, deleting an entry
  8.  
  9. function escapeKey(key) {
  10. if (key.indexOf('__proto__') === 0) { // (1)
  11. return key+'%';
  12. } else {
  13. return key;
  14. }
  15. }

We also need to escape the escaped version of 'proto' (etc.) to avoid clashes; that is, if we escape the key 'proto' as 'proto%', then we also need to escape the key 'proto%' so that it doesn’t replace a 'proto' entry. That’s what happens in line (1).

Mark S. Miller mentions the real-world implications of this pitfall in an email:

Think this exercise is academic and doesn’t arise in real systems? As observed at a support thread, until recently, on all non-IE browsers, if you typed “proto” at the beginning of a new Google Doc, your Google Doc would hang. This was tracked down to such a buggy use of an object as a string map.

The dict Pattern: Objects Without Prototypes Are Better Maps

You create an object without a prototype like this:

  1. var dict = Object.create(null);

Such an object is a better map (dictionary) than a normal object, which is why this pattern is sometimes called the dict pattern (dict for dictionary). Let’s first examine normal objects and then find out why prototype-less objects are better maps.

Normal objects

Usually, each object you create in JavaScript has at least Object.prototype in its prototype chain. The prototype of Object.prototype is null, so that’s where most prototype chains end:

  1. > Object.getPrototypeOf({}) === Object.prototype
  2. true
  3. > Object.getPrototypeOf(Object.prototype)
  4. null

Prototype-less objects

Prototype-less objects have two advantages as maps:

  • Inherited properties (pitfall #1) are not an issue anymore, simply because there are none. Therefore, you can now freely use the in operator to detect whether a property exists and brackets to read properties.
  • Soon, proto will be disabled. In ECMAScript 6, the special property proto will be disabled if Object.prototype is not in the prototype chain of an object. You can expect JavaScript engines to slowly migrate to this behavior, but it is not yet very common.

The only disadvantage is that you’ll lose the services provided by Object.prototype. For example, a dict object can’t be automatically converted to a string anymore:

  1. > console.log('Result: '+obj)
  2. TypeError: Cannot convert object to primitive value

But that is not a real disadvantage, because it isn’t safe to directly invoke methods on a dict object anyway.

Recommendation

Use the dict pattern for quick hacks and as a foundation for libraries. In (nonlibrary) production code, a library is preferable, because you can be sure to avoid all pitfalls. The next section lists a few such libraries.

Best Practices

There are many applications for using objects as maps. If all property keys are known statically (at development time), then you just need to make sure that you ignore inheritance and look only at own properties. If arbitrary keys can be used, you should turn to a library to avoid the pitfalls mentioned in this section. Here are two examples:

Cheat Sheet: Working with Objects

This section is a quick reference with pointers to more thorough explanations.

  1. var jane = {
  2. name: 'Jane',
  3.  
  4. 'not an identifier': 123,
  5.  
  6. describe: function () { // method
  7. return 'Person named '+this.name;
  8. },
  9. };
  10. // Call a method:
  11. console.log(jane.describe()); // Person named Jane
  1. obj.propKey
  2. obj.propKey = value
  3. delete obj.propKey
  1. obj['propKey']
  2. obj['propKey'] = value
  3. delete obj['propKey']
  1. Object.create(proto, propDescObj?)
  2. Object.getPrototypeOf(obj)
  1. Object.keys(obj)
  2. Object.getOwnPropertyNames(obj)
  3.  
  4. Object.prototype.hasOwnProperty.call(obj, propKey)
  5. propKey in obj
  1. Object.defineProperty(obj, propKey, propDesc)
  2. Object.defineProperties(obj, propDescObj)
  3. Object.getOwnPropertyDescriptor(obj, propKey)
  4. Object.create(proto, propDescObj?)
  1. Object.preventExtensions(obj)
  2. Object.isExtensible(obj)
  3.  
  4. Object.seal(obj)
  5. Object.isSealed(obj)
  6.  
  7. Object.freeze(obj)
  8. Object.isFrozen(obj)
  1. Object.prototype.toString()
  2. Object.prototype.valueOf()
  3.  
  4. Object.prototype.toLocaleString()
  5.  
  6. Object.prototype.isPrototypeOf(obj)
  7. Object.prototype.hasOwnProperty(key)
  8. Object.prototype.propertyIsEnumerable(propKey)


[17] Using map() in this manner is a tip by Brandon Benvie (@benvie).