There are several layers to object-oriented programming (OOP) in JavaScript:
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.
There are three kinds of properties:
[[Prototype]]
holds the prototype of an object and is readable via Object.getPrototypeOf()
.
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:
var
jane
=
{
name
:
'Jane'
,
describe
:
function
()
{
return
'Person named '
+
this
.
name
;
// (1)
},
// (2)
};
this
in methods to refer to the current object (also called the receiver of a method invocation).
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.
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:
var
jane
=
{
name
:
'Jane'
,
describe
:
function
()
{
return
'Person named '
+
this
.
name
;
}
};
The dot operator lets you “get” a property (read its value). Here are some examples:
> jane.name // get property `name` 'Jane' > jane.describe // get property `describe` [Function]
Getting a property that doesn’t exist returns undefined
:
> jane.unknownProperty undefined
The dot operator is also used to call methods:
> jane.describe() // call method `describe` 'Person named Jane'
You can use the assignment operator (=
) to set the value of a property referred to via the dot notation. For example:
> jane.name = 'John'; // set property `name` > jane.describe() '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.
The delete
operator lets you completely remove a property (the whole key-value pair) from an object. For example:
> var obj = { hello: 'world' }; > delete obj.hello true > obj.hello undefined
If you merely set a property to undefined
, the property still exists and the object still contains its key:
> var obj = { foo: 'a', bar: 'b' }; > obj.foo = undefined; > Object.keys(obj) [ 'foo', 'bar' ]
If you delete the property, its key is gone, too:
> delete obj.foo true > Object.keys(obj) [ 'bar' ]
delete
affects only the direct (“own,” noninherited) properties of an object. Its prototypes are not touched (see Deleting an inherited property).
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.
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()
):
var
obj
=
{};
Object
.
defineProperty
(
obj
,
'canBeDeleted'
,
{
value
:
123
,
configurable
:
true
});
Object
.
defineProperty
(
obj
,
'cannotBeDeleted'
,
{
value
:
456
,
configurable
:
false
});
delete
returns false
for own properties that can’t be deleted:
> delete obj.cannotBeDeleted false
delete
returns true
in all other cases:
> delete obj.doesNotExist true > delete obj.canBeDeleted true
delete
returns true
even if it doesn’t change anything (inherited properties are never removed):
> delete obj.toString true > obj.toString // still there [Function: toString]
While you can’t use reserved words (such as var
and function
) as variable names, you can use them as property keys:
> var obj = { var: 'a', function: 'b' }; > obj.var 'a' > obj.function '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:
> var obj = { 0.7: 'abc' }; > Object.keys(obj) [ '0.7' ] > obj['0.7'] '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:
> var obj = { 'not an identifier': 123 }; > Object.keys(obj) [ 'not an identifier' ] > obj['not an identifier'] 123
While the dot operator works with fixed property keys, the bracket operator allows you to refer to a property via an expression.
The bracket operator lets you compute the key of a property, via an expression:
> var obj = { someProperty: 'abc' }; > obj['some' + 'Property'] 'abc' > var propKey = 'someProperty'; > obj[propKey] 'abc'
That also allows you to access properties whose keys are not identifiers:
> var obj = { 'not an identifier': 123 }; > obj['not an identifier'] 123
Note that the bracket operator coerces its interior to string. For example:
> var obj = { '6': 'bar' }; > obj[3+3] // key: the string '6' 'bar'
Calling methods works as you would expect:
> var obj = { myMethod: function () { return true } }; > obj['myMethod']() true
Setting properties works analogously to the dot operator:
> var obj = {}; > obj['anotherProperty'] = 'def'; > obj.anotherProperty 'def'
Deleting properties also works similarly to the dot operator:
> var obj = { 'not an identifier': 1, prop: 2 }; > Object.keys(obj) [ 'not an identifier', 'prop' ] > delete obj['not an identifier'] true > Object.keys(obj) [ 'prop' ]
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) |
|
|
|
|
|
A boolean |
|
A number |
|
A string |
|
An object |
|
Here are some examples:
> Object(null) instanceof Object true > Object(false) instanceof Boolean true > var obj = {}; > Object(obj) === obj true
The following function checks whether value
is an object:
function
isObject
(
value
)
{
return
value
===
Object
(
value
);
}
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:
> var obj = {}; > new Object(obj) === obj true > new Object(123) instanceof Number true
Avoid the constructor; an empty object literal is almost always a better choice:
var
obj
=
new
Object
();
// avoid
var
obj
=
{};
// prefer
When you call a function, this
is always an (implicit) parameter:
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):
> function returnThisSloppy() { return this } > returnThisSloppy() === window true
this
is always undefined
:
> function returnThisStrict() { 'use strict'; return this } > returnThisStrict() === undefined true
this
refers to the object on which the method has been invoked:
> var obj = { method: returnThisStrict }; > obj.method() === obj true
In the case of methods, the value of this
is called the receiver of the method call.
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
:
var
jane
=
{
name
:
'Jane'
,
sayHelloTo
:
function
(
otherName
)
{
'use strict'
;
console
.
log
(
this
.
name
+
' says hello to '
+
otherName
);
}
};
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:
jane
.
sayHelloTo
(
'Tarzan'
);
jane
.
sayHelloTo
.
call
(
jane
,
'Tarzan'
);
var
func
=
jane
.
sayHelloTo
;
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.
jane
.
sayHelloTo
(
'Tarzan'
);
jane
.
sayHelloTo
.
apply
(
jane
,
[
'Tarzan'
]);
var
func
=
jane
.
sayHelloTo
;
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.
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:
function
func
()
{
console
.
log
(
'this: '
+
this
);
console
.
log
(
'arguments: '
+
Array
.
prototype
.
slice
.
call
(
arguments
));
}
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:
> bound(3) this: abc arguments: 1,2,3
The following three invocations of sayHelloTo
are all equivalent:
jane
.
sayHelloTo
(
'Tarzan'
);
var
func1
=
jane
.
sayHelloTo
.
bind
(
jane
);
func1
(
'Tarzan'
);
var
func2
=
jane
.
sayHelloTo
.
bind
(
jane
,
'Tarzan'
);
func2
();
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:
Math
.
max
(...[
13
,
7
,
30
])
Math
.
max
(
13
,
7
,
30
)
For functions, you can achieve the effect of the triple dot operator via apply()
:
> Math.max.apply(null, [13, 7, 30]) 30
The triple dot operator would also make sense for constructors:
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.
We can simulate apply()
in two steps.
Pass the arguments to Date
via a method call (they are not in an array—yet):
new
(
Date
.
bind
(
null
,
2011
,
11
,
24
))
The preceding code uses bind()
to create a constructor without parameters and invokes it via new
.
Use apply()
to hand an array to bind()
. Because bind()
is a method call, we can use apply()
:
new
(
Function
.
prototype
.
bind
.
apply
(
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
:
var
arr
=
[
2011
,
11
,
24
];
new
(
Function
.
prototype
.
bind
.
apply
(
Date
,
[
null
].
concat
(
arr
)))
The preceding manual workaround is inspired by a library method published by Mozilla. The following is a slightly edited version of it:
if
(
!
Function
.
prototype
.
construct
)
{
Function
.
prototype
.
construct
=
function
(
argArray
)
{
if
(
!
Array
.
isArray
(
argArray
))
{
throw
new
TypeError
(
"Argument must be an array"
);
}
var
constr
=
this
;
var
nullaryFunc
=
Function
.
prototype
.
bind
.
apply
(
constr
,
[
null
].
concat
(
argArray
));
return
new
nullaryFunc
();
};
}
Here is the method in use:
> Date.construct([2011, 11, 24]) Sat Dec 24 2011 00:00:00 GMT+0100 (CET)
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):
Function
.
prototype
.
construct
=
function
(
argArray
)
{
var
constr
=
this
;
var
inst
=
Object
.
create
(
constr
.
prototype
);
var
result
=
constr
.
apply
(
inst
,
argArray
);
// (1)
// Check: did the constructor return an object
// and prevent `this` from being the result?
return
result
?
result
:
inst
;
};
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.
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
:
var
counter
=
{
count
:
0
,
inc
:
function
()
{
this
.
count
++
;
}
}
Extracting inc
and calling it (as a function!) fails:
> var func = counter.inc; > func() > counter.count // didn’t work 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
:
> count // global variable NaN
If method inc()
is in strict mode, you get a warning:
> counter.inc = function () { 'use strict'; this.count++ }; > var func2 = counter.inc; > func2() 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.
Thanks to bind()
, we can make sure that inc
doesn’t lose the connection with counter
:
> var func3 = counter.inc.bind(counter); > func3() > counter.count // it worked! 1
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:
function
callIt
(
callback
)
{
callback
();
}
Executing counter.count
via callIt
triggers a warning (due to strict mode):
> callIt(counter.inc) TypeError: Cannot read property 'count' of undefined
As before, we fix things via bind()
:
> callIt(counter.inc.bind(counter)) > counter.count // one more than before 2
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.
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):
var
obj
=
{
name
:
'Jane'
,
friends
:
[
'Tarzan'
,
'Cheeta'
],
loop
:
function
()
{
'use strict'
;
this
.
friends
.
forEach
(
function
(
friend
)
{
// (1)
console
.
log
(
this
.
name
+
' knows '
+
friend
);
// (2)
}
);
}
};
This fails, because the function at (1) has its own this
, which is undefined
here:
> obj.loop(); TypeError: Cannot read property 'name' of undefined
There are three ways to work around this problem.
We assign this
to a variable that won’t be shadowed inside the nested function:
loop
:
function
()
{
'use strict'
;
var
that
=
this
;
this
.
friends
.
forEach
(
function
(
friend
)
{
console
.
log
(
that
.
name
+
' knows '
+
friend
);
});
}
Here’s the interaction:
> obj.loop(); Jane knows Tarzan Jane knows Cheeta
We can use bind()
to give the callback a fixed value for this
—namely, the method’s this
(line (1)):
loop
:
function
()
{
'use strict'
;
this
.
friends
.
forEach
(
function
(
friend
)
{
console
.
log
(
this
.
name
+
' knows '
+
friend
);
}.
bind
(
this
));
// (1)
}
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:
loop
:
function
()
{
'use strict'
;
this
.
friends
.
forEach
(
function
(
friend
)
{
console
.
log
(
this
.
name
+
' knows '
+
friend
);
},
this
);
}
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).
To see how prototype-based (or prototypal) inheritance works, let’s look at an example (with invented syntax for specifying the [[Prototype]]
property):
var
proto
=
{
describe
:
function
()
{
return
'name: '
+
this
.
name
;
}
};
var
obj
=
{
[[
Prototype
]]
:
proto
,
name
:
'obj'
};
The object obj
inherits the property describe
from proto
. It also has a so-called own (noninherited, direct) property, name
.
obj
inherits the property describe
; you can access it as if the object itself had that property:
> obj.describe [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:
> obj.describe() 'name: obj'
Inside describe()
, this
is obj
, which allows the method to access obj.name
.
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
:
> obj.describe = function () { return 'overridden' }; > obj.describe() 'overridden'
That is similar to how overriding of methods works in class-based languages.
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:
var
jane
=
{
name
:
'Jane'
,
describe
:
function
()
{
return
'Person named '
+
this
.
name
;
}
};
var
tarzan
=
{
name
:
'Tarzan'
,
describe
:
function
()
{
return
'Person named '
+
this
.
name
;
}
};
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 following code creates objects jane
and tarzan
that share the prototype PersonProto
:
var
PersonProto
=
{
describe
:
function
()
{
return
'Person named '
+
this
.
name
;
}
};
var
jane
=
{
[[
Prototype
]]
:
PersonProto
,
name
:
'Jane'
};
var
tarzan
=
{
[[
Prototype
]]
:
PersonProto
,
name
:
'Tarzan'
};
And here is the interaction:
> jane.describe() Person named Jane > tarzan.describe() 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).
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.
This invocation:
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):
var
PersonProto
=
{
describe
:
function
()
{
return
'Person named '
+
this
.
name
;
}
};
var
jane
=
Object
.
create
(
PersonProto
,
{
name
:
{
value
:
'Jane'
,
writable
:
true
}
});
Here is the interaction:
> jane.describe() 'Person named Jane'
But you frequently just create an empty object and then manually add properties, because descriptors are verbose:
var
jane
=
Object
.
create
(
PersonProto
);
jane
.
name
=
'Jane'
;
This method call:
Object
.
getPrototypeOf
(
obj
)
returns the prototype of obj
. Continuing the preceding example:
> Object.getPrototypeOf(jane) === PersonProto true
This syntax:
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:
> var A = {}; > var B = Object.create(A); > var C = Object.create(B); > A.isPrototypeOf(C) true > C.isPrototypeOf(A) false
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:
function
getDefiningObject
(
obj
,
propKey
)
{
obj
=
Object
(
obj
);
// make sure it’s an object
while
(
obj
&&
!
{}.
hasOwnProperty
.
call
(
obj
,
propKey
))
{
obj
=
Object
.
getPrototypeOf
(
obj
);
// obj is null if we have reached the end
}
return
obj
;
}
In the preceding code, we called the method Object.prototype.hasOwnProperty
generically (see Generic Methods: Borrowing Methods from Prototypes).
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:
> var obj = {}; > obj.__proto__ === Object.prototype true > obj.__proto__ = Array.prototype > Object.getPrototypeOf(obj) === Array.prototype 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.
__proto__
and it will be part of ECMAScript 6.
The following expression checks whether an engine supports __proto__
as a special property:
Object
.
getPrototypeOf
({
__proto__
:
null
})
===
null
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 creates an own property, even if there is an inherited property with that key. For example, given the following source code:
var
proto
=
{
foo
:
'a'
};
var
obj
=
Object
.
create
(
proto
);
obj
inherits foo
from proto
:
> obj.foo 'a' > obj.hasOwnProperty('foo') false
Setting foo
has the desired result:
> obj.foo = 'b'; > obj.foo 'b'
However, we have created an own property and not changed proto.foo
:
> obj.hasOwnProperty('foo') true > proto.foo '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.
You can only delete own properties. Let’s again set up an object, obj
, with a prototype, proto
:
var
proto
=
{
foo
:
'a'
};
var
obj
=
Object
.
create
(
proto
);
Deleting the inherited property foo
has no effect:
> delete obj.foo true > obj.foo 'a'
For more information on the delete
operator, consult Deleting properties.
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:
> delete getDefiningObject(obj, 'foo').foo; true > obj.foo undefined
Operations for iterating over and detecting properties are influenced by:
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.
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.
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:
for
(
«
variable
»
in
«
object
»
)
«
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:
function
getAllPropertyNames
(
obj
)
{
var
result
=
[];
while
(
obj
)
{
// Add the own property names of `obj` to `result`
result
=
result
.
concat
(
Object
.
getOwnPropertyNames
(
obj
));
obj
=
Object
.
getPrototypeOf
(
obj
);
}
return
result
;
}
You can check whether an object has a property, or whether a property exists directly inside an object:
propKey in obj
true
if obj
has a property whose key is propKey
. Inherited properties are included in this test.
Object.prototype.hasOwnProperty(propKey)
true
if the receiver (this
) has an own (noninherited) property whose key is propKey
.
Avoid invoking hasOwnProperty()
directly on an object, as it may be overridden (e.g., by an own property whose key is hasOwnProperty
):
> var obj = { hasOwnProperty: 1, foo: 2 }; > obj.hasOwnProperty('foo') // unsafe TypeError: Property 'hasOwnProperty' is not a function
Instead, it is better to call it generically (see Generic Methods: Borrowing Methods from Prototypes):
> Object.prototype.hasOwnProperty.call(obj, 'foo') // safe true > {}.hasOwnProperty.call(obj, 'foo') // shorter true
The following examples are based on these definitions:
var
proto
=
Object
.
defineProperties
({},
{
protoEnumTrue
:
{
value
:
1
,
enumerable
:
true
},
protoEnumFalse
:
{
value
:
2
,
enumerable
:
false
}
});
var
obj
=
Object
.
create
(
proto
,
{
objEnumTrue
:
{
value
:
1
,
enumerable
:
true
},
objEnumFalse
:
{
value
:
2
,
enumerable
:
false
}
});
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 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):
> Object.getPrototypeOf({}) === Object.prototype true
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):
> for (var x in obj) console.log(x); objEnumTrue protoEnumTrue
Object.keys()
returns the keys of all own (noninherited) enumerable properties:
> Object.keys(obj) [ 'objEnumTrue' ]
If you want the keys of all own properties, you need to use Object.getOwnPropertyNames()
:
> Object.getOwnPropertyNames(obj) [ 'objEnumTrue', 'objEnumFalse' ]
Only the for-in
loop (see the previous example) and the in
operator consider inheritance:
> 'toString' in obj true > obj.hasOwnProperty('toString') false > obj.hasOwnProperty('objEnumFalse') true
Objects don’t have a method such as length
or size
, so you have to use the following workaround:
Object
.
keys
(
obj
).
length
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:
for
(
var
key
in
obj
)
{
if
(
Object
.
prototype
.
hasOwnProperty
.
call
(
obj
,
key
))
{
console
.
log
(
key
);
}
}
Combine Object.keys()
or Object.getOwnPropertyNames()
with forEach()
array iteration:
var
obj
=
{
first
:
'John'
,
last
:
'Doe'
};
// Visit non-inherited enumerable keys
Object
.
keys
(
obj
).
forEach
(
function
(
key
)
{
console
.
log
(
key
);
});
To iterate over property values or over (key, value) pairs:
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.
The following example uses an object literal to define a setter and a getter for property foo
:
var
obj
=
{
get
foo
()
{
return
'getter'
;
},
set
foo
(
value
)
{
console
.
log
(
'setter: '
+
value
);
}
};
Here’s the interaction:
> obj.foo = 'bla'; setter: bla > obj.foo 'getter'
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:
var
obj
=
Object
.
create
(
Object
.
prototype
,
{
// object with property descriptors
foo
:
{
// property descriptor
get
:
function
()
{
return
'getter'
;
},
set
:
function
(
value
)
{
console
.
log
(
'setter: '
+
value
);
}
}
}
);
Getters and setters are inherited from prototypes:
> var proto = { get foo() { return 'hello' } }; > var obj = Object.create(proto); > obj.foo 'hello'
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:
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.
If you don’t specify attributes, the following defaults are used:
Attribute key | Default value |
|
|
|
|
|
|
|
|
|
|
|
|
These defaults are important when you are creating properties via property descriptors (see the following section).
{
value
:
123
,
writable
:
false
,
enumerable
:
true
,
configurable
:
false
}
You can achieve the same goal, immutability, via accessors. Then the descriptor looks as follows:
{
get
:
function
()
{
return
123
},
enumerable
:
true
,
configurable
:
false
}
Property descriptors are used for two kinds of operations:
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:
> var obj = {}; > Object.defineProperty(obj, 'foo', { configurable: true }); > Object.getOwnPropertyDescriptor(obj, 'foo') { value: undefined, writable: false, enumerable: false, 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):
> Object.defineProperty(obj, 'foo', { writable: true }); > Object.getOwnPropertyDescriptor(obj, 'foo') { value: undefined, writable: true, enumerable: false, 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:
> Object.getOwnPropertyDescriptor(Object.prototype, 'toString') { value: [Function: toString], writable: true, enumerable: false, configurable: true } > Object.getOwnPropertyDescriptor({}, 'toString') 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:
var
obj
=
Object
.
defineProperty
({},
'foo'
,
{
value
:
123
,
enumerable
:
true
// writable: false (default value)
// configurable: false (default value)
});
Object.defineProperties(obj, propDescObj)
var
obj
=
Object
.
defineProperties
({},
{
foo
:
{
value
:
123
,
enumerable
:
true
},
bar
:
{
value
:
'abc'
,
enumerable
:
true
}
});
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:
var
obj
=
Object
.
create
(
Object
.
prototype
,
{
foo
:
{
value
:
123
,
enumerable
:
true
},
bar
:
{
value
:
'abc'
,
enumerable
:
true
}
});
To create an identical copy of an object, you need to get two things right:
The following function performs such a copy:
function
copyObject
(
orig
)
{
// 1. copy has same prototype as orig
var
copy
=
Object
.
create
(
Object
.
getPrototypeOf
(
orig
));
// 2. copy has all of orig’s properties
copyOwnPropertiesFrom
(
copy
,
orig
);
return
copy
;
}
The properties are copied from orig
to copy
via this function:
function
copyOwnPropertiesFrom
(
target
,
source
)
{
Object
.
getOwnPropertyNames
(
source
)
// (1)
.
forEach
(
function
(
propKey
)
{
// (2)
var
desc
=
Object
.
getOwnPropertyDescriptor
(
source
,
propKey
);
// (3)
Object
.
defineProperty
(
target
,
propKey
,
desc
);
// (4)
});
return
target
;
};
These are the steps involved:
source
.
target
.
Note that this function is very similar to the function _.extend()
in the Underscore.js library.
The following two operations are very similar:
defineProperty()
and defineProperties()
(see Getting and Defining Properties via Descriptors).
=
.
There are, however, a few subtle differences:
Assigning to a property prop
means changing an existing property. The process is as follows:
prop
is a setter (own or inherited), call that setter.
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.
prop
is own (and writable), change the value of that 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.
If an object, obj
, inherits a property, foo
, from a prototype and foo
is not writable, then you can’t assign to obj.foo
:
var
proto
=
Object
.
defineProperty
({},
'foo'
,
{
value
:
'a'
,
writable
:
false
});
var
obj
=
Object
.
create
(
proto
);
obj
inherits the read-only property foo
from proto
. In sloppy mode, setting the property has no effect:
> obj.foo = 'b'; > obj.foo 'a'
In strict mode, you get an exception:
> (function () { 'use strict'; obj.foo = 'b' }()); 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):
> Object.defineProperty(obj, 'foo', { value: 'b' }); > obj.foo 'b'
> Object.keys([]) [] > Object.getOwnPropertyNames([]) [ 'length' ] > Object.keys(['a']) [ '0' ]
This is especially true for the methods of the built-in instance prototypes:
> Object.keys(Object.prototype) [] > Object.getOwnPropertyNames(Object.prototype) [ hasOwnProperty', 'valueOf', 'constructor', 'toLocaleString', 'isPrototypeOf', 'propertyIsEnumerable', '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:
for-in
loop
Object.keys()
(Listing Own Property Keys)
JSON.stringify()
(JSON.stringify(value, replacer?, space?))
Here are some best practices to keep in mind:
for-in
loop (Best Practices: Iterating over Arrays).
There are three levels of protecting an object, listed here from weakest to strongest:
Preventing extensions via:
Object
.
preventExtensions
(
obj
)
makes it impossible to add properties to obj
. For example:
var
obj
=
{
foo
:
'a'
};
Object
.
preventExtensions
(
obj
);
Now adding a property fails silently in sloppy mode:
> obj.bar = 'b'; > obj.bar undefined
and throws an error in strict mode:
> (function () { 'use strict'; obj.bar = 'b' }()); TypeError: Can't add property bar, object is not extensible
You can still delete properties, though:
> delete obj.foo true > obj.foo undefined
You check whether an object is extensible via:
Object
.
isExtensible
(
obj
)
Sealing via:
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:
> var obj = { foo: 'a' }; > Object.getOwnPropertyDescriptor(obj, 'foo') // before sealing { value: 'a', writable: true, enumerable: true, configurable: true } > Object.seal(obj) > Object.getOwnPropertyDescriptor(obj, 'foo') // after sealing { value: 'a', writable: true, enumerable: true, configurable: false }
You can still change the property foo
:
> obj.foo = 'b'; 'b' > obj.foo 'b'
but you can’t change its attributes:
> Object.defineProperty(obj, 'foo', { enumerable: false }); TypeError: Cannot redefine property: foo
You check whether an object is sealed via:
Object
.
isSealed
(
obj
)
Freezing is performed via:
Object
.
freeze
(
obj
)
var
point
=
{
x
:
17
,
y
:
-
5
};
Object
.
freeze
(
point
);
Once again, you get silent failures in sloppy mode:
> point.x = 2; // no effect, point.x is read-only > point.x 17 > point.z = 123; // no effect, point is not extensible > point { x: 17, y: -5 }
And you get errors in strict mode:
> (function () { 'use strict'; point.x = 2 }()); TypeError: Cannot assign to read-only property 'x' > (function () { 'use strict'; point.z = 123 }()); TypeError: Can't add property z, object is not extensible
You check whether an object is frozen via:
Object
.
isFrozen
(
obj
)
Protecting an object is shallow: it affects the own properties, but not the values of those properties. For example, consider the following object:
var
obj
=
{
foo
:
1
,
bar
:
[
'a'
,
'b'
]
};
Object
.
freeze
(
obj
);
Even though you have frozen obj
, it is not completely immutable—you can change the (mutable) value of property bar
:
> obj.foo = 2; // no effect > obj.bar.push('c'); // changes obj.bar > obj { foo: 1, bar: [ 'a', 'b', 'c' ] }
Additionally, obj
has the prototype Object.prototype
, which is also mutable.
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):
var
PersonProto
=
{
describe
:
function
()
{
return
'Person named '
+
this
.
name
;
}
};
var
jane
=
{
[[
Prototype
]]
:
PersonProto
,
name
:
'Jane'
};
var
tarzan
=
{
[[
Prototype
]]
:
PersonProto
,
name
:
'Tarzan'
};
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:
jane
and tarzan
in the preceding example).
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:
function
Person
(
name
)
{
this
.
name
=
name
;
}
The object in Person.prototype
becomes the prototype of all instances of Person
. It contributes part 2:
Person
.
prototype
.
describe
=
function
()
{
return
'Person named '
+
this
.
name
;
};
Let’s create and use an instance of Person
:
> var jane = new Person('Jane'); > jane.describe() '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:
Person.
prototype
.
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.
The instanceof
operator allows us to check whether an object is an instance of a given constructor:
> jane instanceof Person true > jane instanceof Date false
If you were to manually implement the new
operator, it would look roughly as follows:
function
newOperator
(
Constr
,
args
)
{
var
thisValue
=
Object
.
create
(
Constr
.
prototype
);
// (1)
var
result
=
Constr
.
apply
(
thisValue
,
args
);
if
(
typeof
result
===
'object'
&&
result
!==
null
)
{
return
result
;
// (2)
}
return
thisValue
;
}
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).
Unfortunately, the term prototype is used ambiguously in JavaScript:
An object can be the prototype of another object:
> var proto = {}; > var obj = Object.create(proto); > Object.getPrototypeOf(obj) === proto true
In the preceding example, proto
is the prototype of obj
.
prototype
Each constructor C
has a prototype
property that refers to an object. That object becomes the prototype of all instances of C
:
> function C() {} > Object.getPrototypeOf(new C()) === C.prototype 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:
> function Foo() {} > Object.getPrototypeOf(Foo) === Function.prototype true
Thus, instance prototype is the best option.
By default, each function C
contains an instance prototype object C.prototype
whose property constructor
points back to C
:
> function C() {} > C.prototype.constructor === C true
Because the constructor
property is inherited from the prototype by each instance, you can use it to get the constructor of an instance:
> var o = new C(); > o.constructor [Function: C]
In the following catch
clause, we take different actions, depending on the constructor of the caught exception:
try
{
...
}
catch
(
e
)
{
switch
(
e
.
constructor
)
{
case
SyntaxError
:
...
break
;
case
CustomError
:
...
break
;
...
}
}
This approach detects only direct instances of a given constructor. In contrast, instanceof
detects both direct instances and instances of all subconstructors.
For example:
> function Foo() {} > var f = new Foo(); > f.constructor.name 'Foo'
Not all JavaScript engines support the property name
for functions.
This is how you create a new object, y
, that has the same constructor as an existing object, x
:
function
Constr
()
{}
var
x
=
new
Constr
();
var
y
=
new
x
.
constructor
();
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:
SuperConstr
.
prototype
.
createCopy
=
function
()
{
return
new
this
.
constructor
(...);
};
Some inheritance libraries assign the superprototype to a property of a subconstructor. For example, the YUI framework provides subclassing via Y.extend
:
function
Super
()
{
}
function
Sub
()
{
Sub
.
superclass
.
constructor
.
call
(
this
);
// (1)
}
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.
The instanceof
operator (see The instanceof Operator) does not rely on the property constructor
.
Make sure that for each constructor C
, the following assertion holds:
C
.
prototype
.
constructor
===
C
By default, every function f
already has a property prototype
that is set up correctly:
> function f() {} > f.prototype.constructor === f true
You should thus avoid replacing this object and only add properties to it:
// Avoid:
C
.
prototype
=
{
method1
:
function
(...)
{
...
},
...
};
// Prefer:
C
.
prototype
.
method1
=
function
(...)
{
...
};
...
If you do replace it, you should manually assign the correct value to constructor
:
C
.
prototype
=
{
constructor
:
C
,
method1
:
function
(...)
{
...
},
...
};
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:
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:
value
instanceof
Constr
Constr
.
prototype
.
isPrototypeOf
(
value
)
Here are some examples:
> {} instanceof Object true > [] instanceof Array // constructor of [] true > [] instanceof Object // super-constructor of [] true > new Date() instanceof Date true > new Date() instanceof Object true
As expected, instanceof
is always false
for primitive values:
> 'abc' instanceof Object false > 123 instanceof Number false
Finally, instanceof
throws an exception if its right side isn’t a function:
> [] instanceof 123 TypeError: Expecting a function in instanceof check
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:
> Object.create(null) instanceof Object false > Object.prototype instanceof Object 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:
> Object.getPrototypeOf(Object.create(null)) null > Object.getPrototypeOf(Object.prototype) null
But typeof
correctly classifies them as objects:
> typeof Object.create(null) 'object' > typeof Object.prototype 'object'
This pitfall is not a deal-breaker for most use cases for instanceof
, but you have to be aware of it.
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:
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:
<head>
<script>
function
test
(
arr
)
{
var
iframe
=
frames
[
0
];
console
.
log
(
arr
instanceof
Array
);
// false
console
.
log
(
arr
instanceof
iframe
.
Array
);
// true
console
.
log
(
Array
.
isArray
(
arr
));
// true
}
</script>
</head>
<body>
<iframe
srcdoc=
"<script>window.parent.test([])</script>"
>
</iframe>
</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:
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):
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.
This section gives a few tips for implementing constructors.
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:
function
SloppyColor
(
name
)
{
this
.
name
=
name
;
}
var
c
=
SloppyColor
(
'green'
);
// no warning!
// No instance is created:
console
.
log
(
c
);
// undefined
// A global variable is created:
console
.
log
(
name
);
// green
In strict mode, you get an exception:
function
StrictColor
(
name
)
{
'use strict'
;
this
.
name
=
name
;
}
var
c
=
StrictColor
(
'green'
);
// TypeError: Cannot set property 'name' of undefined
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:
class
Expression
{
// Static factory method:
public
static
Expression
parse
(
String
str
)
{
if
(...)
{
return
new
Addition
(...);
}
else
if
(...)
{
return
new
Multiplication
(...);
}
else
{
throw
new
ExpressionException
(...);
}
}
}
...
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:
function
Expression
(
str
)
{
if
(...)
{
return
new
Addition
(..);
}
else
if
(...)
{
return
new
Multiplication
(...);
}
else
{
throw
new
ExpressionException
(...);
}
}
...
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.
This section explains that in most cases, you should not put data in prototype properties. There are, however, a few exceptions to that rule.
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:
/**
* Anti-pattern: don’t do this
*
* @param data an array with names
*/
function
Names
(
data
)
{
if
(
data
)
{
// There is a parameter
// => create instance property
this
.
data
=
data
;
}
}
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):
> var n1 = new Names(); > var n2 = new Names(); > n1.data.push('jane'); // changes default value > n1.data [ 'jane' ] > n2.data [ 'jane' ]
In the preceding example, push()
changed the array in Names.prototype.data
. Since
that array is shared by all instances without an own property data
,
n2.data
’s initial value has changed, too.
Given what we’ve just discussed, it is better to not share default values and to always create new ones:
function
Names
(
data
)
{
this
.
data
=
data
||
[];
}
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.
Occasionally, creating a property value is an expensive operation (computationally or storage-wise). In that case, you can create an instance property on demand:
function
Names
(
data
)
{
if
(
data
)
this
.
data
=
data
;
}
Names
.
prototype
=
{
constructor
:
Names
,
// (1)
get
data
()
{
// Define, don’t assign
// => avoid calling the (nonexistent) setter
Object
.
defineProperty
(
this
,
'data'
,
{
value
:
[],
enumerable
:
true
,
configurable
:
false
,
writable
:
false
});
return
this
.
data
;
}
};
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.
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
:
function
Foo
()
{}
Foo
.
prototype
.
FACTOR
=
42
;
Foo
.
prototype
.
compute
=
function
(
x
)
{
return
x
*
this
.
FACTOR
;
};
This constant is not polymorphic. Therefore, you can just as well access it via a variable:
// This code should be inside an IIFE or a module
function
Foo
()
{}
var
FACTOR
=
42
;
Foo
.
prototype
.
compute
=
function
(
x
)
{
return
x
*
FACTOR
;
};
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:
function
ConstrA
()
{
}
ConstrA
.
prototype
.
TYPE_NAME
=
'ConstrA'
;
function
ConstrB
()
{
}
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)).
JavaScript does not have dedicated means for managing private data for an object. This section will describe three techniques for working around that limitation:
Additionally, I will explain how to keep global data private via IIFEs.
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):
The following sections explain each kind of value in more detail.
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:
Constr
.
prototype
.
publicMethod
=
...;
Second, instance properties are unique to each instance. They are added in the constructor and usually hold data (not methods):
function
Constr
(...)
{
this
.
publicData
=
...;
...
}
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:
function
Constr
(...)
{
...
var
that
=
this
;
// make accessible to private functions
var
privateData
=
...;
function
privateFunction
(...)
{
// Access everything
privateData
=
...;
that
.
publicData
=
...;
that
.
publicMethod
(...);
}
...
}
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):
function
Constr
(...)
{
...
this
.
privilegedMethod
=
function
(...)
{
// Access everything
privateData
=
...;
privateFunction
(...);
this
.
publicData
=
...;
this
.
publicMethod
(...);
};
}
The following is an implementation of a StringBuilder
, using the Crockford privacy pattern:
function
StringBuilder
()
{
var
buffer
=
[];
this
.
add
=
function
(
str
)
{
buffer
.
push
(
str
);
};
this
.
toString
=
function
()
{
return
buffer
.
join
(
''
);
};
}
// Can’t put methods in the prototype!
Here is the interaction:
> var sb = new StringBuilder(); > sb.add('Hello'); > sb.add(' world!'); > sb.toString() ’Hello world!’
Here are some points to consider when you are using the Crockford privacy pattern:
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:
function
StringBuilder
()
{
this
.
_buffer
=
[];
}
StringBuilder
.
prototype
=
{
constructor
:
StringBuilder
,
add
:
function
(
str
)
{
this
.
_buffer
.
push
(
str
);
},
toString
:
function
()
{
return
this
.
_buffer
.
join
(
''
);
}
};
Here are some pros and cons of privacy via marked property 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:
var
KEY_BUFFER
=
'_StringBuilder_buffer'
;
We now access the private data via this[KEY_BUFFER]
:
var
StringBuilder
=
function
()
{
var
KEY_BUFFER
=
'_StringBuilder_buffer'
;
function
StringBuilder
()
{
this
[
KEY_BUFFER
]
=
[];
}
StringBuilder
.
prototype
=
{
constructor
:
StringBuilder
,
add
:
function
(
str
)
{
this
[
KEY_BUFFER
].
push
(
str
);
},
toString
:
function
()
{
return
this
[
KEY_BUFFER
].
join
(
''
);
}
};
return
StringBuilder
;
}();
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:
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:
_StringBuilder_buffer_110ec58a-a0f2-4ac4-8393-c866d813b8d1
Long keys with UUIDs make key clashes virtually impossible.
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.
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:
var
obj
=
function
()
{
// open IIFE
// public
var
self
=
{
publicMethod
:
function
(...)
{
privateData
=
...;
privateFunction
(...);
},
publicData
:
...
};
// private
var
privateData
=
...;
function
privateFunction
(...)
{
privateData
=
...;
self
.
publicData
=
...;
self
.
publicMethod
(...);
}
return
self
;
}();
// close IIFE
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:
var
StringBuilder
=
function
()
{
// open IIFE
var
KEY_BUFFER
=
'_StringBuilder_buffer_'
+
uuid
.
v4
();
function
StringBuilder
()
{
this
[
KEY_BUFFER
]
=
[];
}
StringBuilder
.
prototype
=
{
// Omitted: methods accessing this[KEY_BUFFER]
};
return
StringBuilder
;
}();
// 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.
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:
var
obj
=
{
method
:
function
()
{
// open IIFE
// method-private data
var
invocCount
=
0
;
return
function
()
{
invocCount
++
;
console
.
log
(
'Invocation #'
+
invocCount
);
return
'result'
;
};
}()
// close IIFE
};
Here is the interaction:
> obj.method() Invocation #1 'result' > obj.method() Invocation #2 'result'
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:
instanceof
works: if sub
is an instance of Sub
, we also want sub instanceof Super
to be true.
Super
’s methods in Sub
.
Super
’s methods, we may need to call the original method from Sub
.
Instance properties are set up in the constructor itself, so inheriting the superconstructor’s instance properties involves calling that constructor:
function
Sub
(
prop1
,
prop2
,
prop3
,
prop4
)
{
Super
.
call
(
this
,
prop1
,
prop2
);
// (1)
this
.
prop3
=
prop3
;
// (2)
this
.
prop4
=
prop4
;
// (3)
}
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
.
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
.
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:
Sub
.
prototype
=
Object
.
create
(
Super
.
prototype
);
Sub
.
prototype
.
constructor
=
Sub
;
Sub
.
prototype
.
methodB
=
...;
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.
“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
.
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):
subInstance
instanceof
Sub
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:
subInstance
instanceof
Super
Super
.
prototype
.
isPrototypeOf
(
subInstance
)
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
.
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:
foo
.
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:
Sub
.
prototype
.
methodB
=
function
(
x
,
y
)
{
var
superResult
=
Super
.
prototype
.
methodB
.
call
(
this
,
x
,
y
);
// (1)
return
this
.
prop3
+
' '
+
superResult
;
}
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
.
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
:
Sub
.
_super
=
Super
.
prototype
;
Then calling the superconstructor and a supermethod looks as follows:
function
Sub
(
prop1
,
prop2
,
prop3
,
prop4
)
{
Sub
.
_super
.
constructor
.
call
(
this
,
prop1
,
prop2
);
this
.
prop3
=
prop3
;
this
.
prop4
=
prop4
;
}
Sub
.
prototype
.
methodB
=
function
(
x
,
y
)
{
var
superResult
=
Sub
.
_super
.
methodB
.
call
(
this
,
x
,
y
);
return
this
.
prop3
+
' '
+
superResult
;
}
Setting up Sub._super
is usually handled by a utility function that also connects the subprototype to the superprototype. For example:
function
subclasses
(
SubC
,
SuperC
)
{
var
subProto
=
Object
.
create
(
SuperC
.
prototype
);
// Save `constructor` and, possibly, other methods
copyOwnPropertiesFrom
(
subProto
,
SubC
.
prototype
);
SubC
.
prototype
=
subProto
;
SubC
.
_super
=
SuperC
.
prototype
;
};
This code uses the helper function copyOwnPropertiesFrom()
, which is shown and explained in Copying an Object.
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.
As a concrete example, let’s assume that the constructor Person
already exists:
function
Person
(
name
)
{
this
.
name
=
name
;
}
Person
.
prototype
.
describe
=
function
()
{
return
'Person called '
+
this
.
name
;
};
We now want to create the constructor Employee
as a subconstructor of Person
. We do so manually, which looks like this:
function
Employee
(
name
,
title
)
{
Person
.
call
(
this
,
name
);
this
.
title
=
title
;
}
Employee
.
prototype
=
Object
.
create
(
Person
.
prototype
);
Employee
.
prototype
.
constructor
=
Employee
;
Employee
.
prototype
.
describe
=
function
()
{
return
Person
.
prototype
.
describe
.
call
(
this
)
+
' ('
+
this
.
title
+
')'
;
};
Here is the interaction:
> var jane = new Employee('Jane', 'CTO'); > jane.describe() Person called Jane (CTO) > jane instanceof Employee true > jane instanceof Person true
The utility function subclasses()
from the previous section makes the code of Employee
slightly simpler and avoids hardcoding the superconstructor Person
:
function
Employee
(
name
,
title
)
{
Employee
.
_super
.
constructor
.
call
(
this
,
name
);
this
.
title
=
title
;
}
Employee
.
prototype
.
describe
=
function
()
{
return
Employee
.
_super
.
describe
.
call
(
this
)
+
' ('
+
this
.
title
+
')'
;
};
subclasses
(
Employee
,
Person
);
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:
> var p = Object.getPrototypeOf > p([]) === Array.prototype true > p(p([])) === Object.prototype true > p(p(p([]))) === null true
Before ECMAScript 5 and Object.create()
, an often-used solution was to create the subprototype by invoking the superconstructor:
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()
).
Almost all objects have Object.prototype
in their prototype chain:
> Object.prototype.isPrototypeOf({}) true > Object.prototype.isPrototypeOf([]) true > Object.prototype.isPrototypeOf(/xyz/) true
The following subsections describe the methods that Object.prototype
provides for its prototypees.
The following two methods are used to convert an object to a primitive value:
Object.prototype.toString()
Returns a string representation of an object:
> ({ first: 'John', last: 'Doe' }.toString()) '[object Object]' > [ 'a', 'b', 'c' ].toString() 'a,b,c'
Object.prototype.valueOf()
This is the preferred way of converting an object to a number. The default implementation returns this
:
> var obj = {}; > obj.valueOf() === obj true
valueOf
is overridden by wrapper constructors to return the wrapped primitive:
> new Number(7).valueOf() 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:
> 3 * { valueOf: function () { return 5 } } 15
toString()
is preferred by the conversion to string:
> String({ toString: function () { return 'ME' } }) 'Result: ME'
The conversion to boolean is not configurable; objects are always considered to be true
(see Converting to Boolean).
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.
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
:
> var proto = { }; > var obj = Object.create(proto); > proto.isPrototypeOf(obj) true > obj.isPrototypeOf(obj) 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.
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:
> var proto = { foo: 'abc' }; > var obj = Object.create(proto); > obj.bar = 'def'; > Object.prototype.hasOwnProperty.call(obj, 'foo') false > Object.prototype.hasOwnProperty.call(obj, 'bar') true
Object.prototype.propertyIsEnumerable(propKey)
Returns true
if the receiver has a property with the key propKey
that is enumerable and false
otherwise:
> var obj = { foo: 'abc' }; > obj.propertyIsEnumerable('foo') true > obj.propertyIsEnumerable('toString') false > obj.propertyIsEnumerable('unknown') false
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()
:
function
Wine
(
age
)
{
this
.
age
=
age
;
}
Wine
.
prototype
.
incAge
=
function
(
years
)
{
this
.
age
+=
years
;
}
The interaction is as follows:
> var chablis = new Wine(3); > chablis.incAge(1); > chablis.age 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:
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()):
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
:
> var john = { age: 51 }; > Wine.prototype.incAge.call(john, 3) > john.age 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).
Calling a method generically is quite verbose:
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 {}
:
{}.
hasOwnProperty
.
call
(
obj
,
'propKey'
)
Similarly, the following two expressions are equivalent:
Array
.
prototype
.
join
.
call
(
str
,
'-'
)
[].
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.
These are a few examples of generic methods in use:
Use apply()
(see Function.prototype.apply(thisValue, argArray)) to push an array (instead of individual elements; see Adding and Removing Elements (Destructive)):
> var arr1 = [ 'a', 'b' ]; > var arr2 = [ 'c', 'd' ]; > [].push.apply(arr1, arr2) 4 > arr1 [ '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):
> Array.prototype.join.call('abc', '-') 'a-b-c'
Apply the array method map()
to a string:[17]
> [].map.call('abc', function (x) { return x.toUpperCase() }) [ 'A', 'B', 'C' ]
Using map()
generically is more efficient than using split('')
, which creates an intermediate array:
> 'abc'.split('').map(function (x) { return x.toUpperCase() }) [ 'A', 'B', 'C' ]
Apply a string method to nonstrings. toUpperCase()
converts the receiver to a string and uppercases the result:
> String.prototype.toUpperCase.call(true) 'TRUE' > String.prototype.toUpperCase.call(['a','b','c']) '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:
> var fakeArray = { 0: 'a', 1: 'b', length: 2 }; > Array.prototype.join.call(fakeArray, '-') 'a-b'
See how an array method transforms an object that it treats like an array:
> var obj = {}; > Array.prototype.push.call(obj, 'hello'); 1 > obj { '0': 'hello', length: 1 }
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:
The special variable arguments
(see All Parameters by Index: The Special Variable arguments), which is an important array-like object, because it is such a fundamental part of JavaScript. arguments
looks like an array:
> function args() { return arguments } > var arrayLike = args('a', 'b'); > arrayLike[0] 'a' > arrayLike.length 2
But none of the array methods are available:
> arrayLike.join('-') 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):
> arrayLike instanceof Array false
Browser DOM node lists, which are returned by document.getElementsBy*()
(e.g., getElementsByTagName()
), document.forms
, and so on:
> var elts = document.getElementsByTagName('h3'); > elts.length 3 > elts instanceof Array false
Strings, which are array-like, too:
> 'abc'[1] 'b' > 'abc'.length 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:
> var obj = { '0': 'abc' }; > obj[0] 'abc'
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.
The following patterns are useful for working with array-like objects:
Turn an array-like object into an array:
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:
var
copy
=
[
'a'
,
'b'
].
slice
();
To iterate over all elements of an array-like object, you can use a simple for
loop:
function
logArgs
()
{
for
(
var
i
=
0
;
i
<
arguments
.
length
;
i
++
)
{
console
.
log
(
i
+
'. '
+
arguments
[
i
]);
}
}
But you can also borrow Array.prototype.forEach()
:
function
logArgs
()
{
Array
.
prototype
.
forEach
.
call
(
arguments
,
function
(
elem
,
i
)
{
console
.
log
(
i
+
'. '
+
elem
);
});
}
In both cases, the interaction looks as follows:
> logArgs('hello', 'world'); 0. hello 1. world
The following list includes all methods that are generic, as mentioned in the ECMAScript language specification:
Array.prototype
(see Array Prototype Methods):
concat
every
filter
forEach
indexOf
join
lastIndexOf
map
pop
push
reduce
reduceRight
reverse
shift
slice
some
sort
splice
toLocaleString
toString
unshift
Date.prototype
(see Date Prototype Methods)
toJSON
Object.prototype
(see Methods of All Objects)
Object
methods are automatically generic—they have to work for all objects.)
String.prototype
(see String Prototype Methods)
charAt
charCodeAt
concat
indexOf
lastIndexOf
localeCompare
match
replace
search
slice
split
substring
toLocaleLowerCase
toLocaleUpperCase
toLowerCase
toUpperCase
trim
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.
The operations that read properties can be partitioned into two kinds:
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:
var
proto
=
{
protoProp
:
'a'
};
var
obj
=
Object
.
create
(
proto
);
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:
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.
The in
operator checks whether an object has a property with a given key, but it considers inherited properties:
> 'ownProp' in obj // ok true > 'unknown' in obj // ok false > 'toString' in obj // wrong, inherited from Object.prototype true > 'protoProp' in obj // wrong, inherited from proto true
We need the check to ignore inherited properties. hasOwnProperty()
does what we want:
> obj.hasOwnProperty('ownProp') // ok true > obj.hasOwnProperty('unknown') // ok false > obj.hasOwnProperty('toString') // ok false > obj.hasOwnProperty('protoProp') // ok false
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:
> for (propKey in obj) console.log(propKey) ownProp 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:
> Object.keys(obj) [ '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()
.
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:
> obj['toString'] [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:
function
getOwnProperty
(
obj
,
propKey
)
{
// Using hasOwnProperty() in this manner is problematic
// (explained and fixed later)
return
(
obj
.
hasOwnProperty
(
propKey
)
?
obj
[
propKey
]
:
undefined
);
}
With that function, the inherited property toString
is ignored:
> getOwnProperty(obj, 'toString') undefined
The function getOwnProperty()
invoked the method hasOwnProperty()
on obj
. Normally, that is fine:
> getOwnProperty({ foo: 123 }, 'foo') 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:
> getOwnProperty({ hasOwnProperty: 123 }, 'foo') 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:
function
getOwnProperty
(
obj
,
propKey
)
{
return
(
Object
.
prototype
.
hasOwnProperty
.
call
(
obj
,
propKey
)
?
obj
[
propKey
]
:
undefined
);
}
We have called hasOwnProperty()
generically (see Generic Methods: Borrowing Methods from Prototypes).
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:
function
get
(
obj
,
key
)
{
return
obj
[
escapeKey
(
key
)];
}
function
set
(
obj
,
key
,
value
)
{
obj
[
escapeKey
(
key
)]
=
value
;
}
// Similar: checking if key exists, deleting an entry
function
escapeKey
(
key
)
{
if
(
key
.
indexOf
(
'__proto__'
)
===
0
)
{
// (1)
return
key
+
'%'
;
}
else
{
return
key
;
}
}
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.
You create an object without a prototype like this:
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.
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:
> Object.getPrototypeOf({}) === Object.prototype true > Object.getPrototypeOf(Object.prototype) null
Prototype-less objects have two advantages as maps:
in
operator to detect whether a property exists and brackets to read properties.
__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:
> console.log('Result: '+obj) 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.
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.
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:
This section is a quick reference with pointers to more thorough explanations.
Object literals (see Object Literals):
var
jane
=
{
name
:
'Jane'
,
'not an identifier'
:
123
,
describe
:
function
()
{
// method
return
'Person named '
+
this
.
name
;
},
};
// Call a method:
console
.
log
(
jane
.
describe
());
// Person named Jane
Dot operator (.) (see Dot Operator (.): Accessing Properties via Fixed Keys):
obj
.
propKey
obj
.
propKey
=
value
delete
obj
.
propKey
Bracket operator ([]) (see Bracket Operator ([]): Accessing Properties via Computed Keys):
obj
[
'propKey'
]
obj
[
'propKey'
]
=
value
delete
obj
[
'propKey'
]
Getting and setting the prototype (see Getting and Setting the Prototype):
Object
.
create
(
proto
,
propDescObj
?
)
Object
.
getPrototypeOf
(
obj
)
Iteration and detection of properties (see Iteration and Detection of Properties):
Object
.
keys
(
obj
)
Object
.
getOwnPropertyNames
(
obj
)
Object
.
prototype
.
hasOwnProperty
.
call
(
obj
,
propKey
)
propKey
in
obj
Getting and defining properties via descriptors (see Getting and Defining Properties via Descriptors):
Object
.
defineProperty
(
obj
,
propKey
,
propDesc
)
Object
.
defineProperties
(
obj
,
propDescObj
)
Object
.
getOwnPropertyDescriptor
(
obj
,
propKey
)
Object
.
create
(
proto
,
propDescObj
?
)
Protecting objects (see Protecting Objects):
Object
.
preventExtensions
(
obj
)
Object
.
isExtensible
(
obj
)
Object
.
seal
(
obj
)
Object
.
isSealed
(
obj
)
Object
.
freeze
(
obj
)
Object
.
isFrozen
(
obj
)
Methods of all objects (see Methods of All Objects):
Object
.
prototype
.
toString
()
Object
.
prototype
.
valueOf
()
Object
.
prototype
.
toLocaleString
()
Object
.
prototype
.
isPrototypeOf
(
obj
)
Object
.
prototype
.
hasOwnProperty
(
key
)
Object
.
prototype
.
propertyIsEnumerable
(
propKey
)