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:
varjane={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:
varjane={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
undefinedIf 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()):
varobj={};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']
123While 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']
123Note 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']()
trueSetting 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
trueThe following function checks whether value is an object:
functionisObject(value){returnvalue===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
trueAvoid the constructor; an empty object literal is almost always a better choice:
varobj=newObject();// avoidvarobj={};// 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
trueIn 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:
varjane={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');varfunc=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']);varfunc=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:
functionfunc(){console.log('this: '+this);console.log('arguments: '+Array.prototype.slice.call(arguments));}varbound=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');varfunc1=jane.sayHelloTo.bind(jane);func1('Tarzan');varfunc2=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:
newDate(...[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:
vararr=[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)){thrownewTypeError("Argument must be an array");}varconstr=this;varnullaryFunc=Function.prototype.bind.apply(constr,[null].concat(argArray));returnnewnullaryFunc();};}
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){varconstr=this;varinst=Object.create(constr.prototype);varresult=constr.apply(inst,argArray);// (1)// Check: did the constructor return an object// and prevent `this` from being the result?returnresult?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:
varcounter={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 undefinedThe 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:
functioncallIt(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):
varobj={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';varthat=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):
varproto={describe:function(){return'name: '+this.name;}};varobj={[[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:
varjane={name:'Jane',describe:function(){return'Person named '+this.name;}};vartarzan={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:
varPersonProto={describe:function(){return'Person named '+this.name;}};varjane={[[Prototype]]:PersonProto,name:'Jane'};vartarzan={[[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):
varPersonProto={describe:function(){return'Person named '+this.name;}};varjane=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:
varjane=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)
falseThe 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:
functiongetDefiningObject(obj,propKey){obj=Object(obj);// make sure it’s an objectwhile(obj&&!{}.hasOwnProperty.call(obj,propKey)){obj=Object.getPrototypeOf(obj);// obj is null if we have reached the end}returnobj;}
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
trueThere 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:
varproto={foo:'a'};varobj=Object.create(proto);
obj inherits foo from proto:
> obj.foo
'a'
> obj.hasOwnProperty('foo')
falseSetting 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:
varproto={foo:'a'};varobj=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:
functiongetAllPropertyNames(obj){varresult=[];while(obj){// Add the own property names of `obj` to `result`result=result.concat(Object.getOwnPropertyNames(obj));obj=Object.getPrototypeOf(obj);}returnresult;}
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 functionInstead, 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
trueThe following examples are based on these definitions:
varproto=Object.defineProperties({},{protoEnumTrue:{value:1,enumerable:true},protoEnumFalse:{value:2,enumerable:false}});varobj=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
trueAmong 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')
trueObjects 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(varkeyinobj){if(Object.prototype.hasOwnProperty.call(obj,key)){console.log(key);}}
Combine Object.keys() or Object.getOwnPropertyNames() with forEach() array iteration:
varobj={first:'John',last:'Doe'};// Visit non-inherited enumerable keysObject.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:
varobj={getfoo(){return'getter';},setfoo(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:
varobj=Object.create(Object.prototype,{// object with property descriptorsfoo:{// property descriptorget: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(){return123},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')
undefinedObject.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:
varobj=Object.defineProperty({},'foo',{value:123,enumerable:true// writable: false (default value)// configurable: false (default value)});
Object.defineProperties(obj, propDescObj)
varobj=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:
varobj=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:
functioncopyObject(orig){// 1. copy has same prototype as origvarcopy=Object.create(Object.getPrototypeOf(orig));// 2. copy has all of orig’s propertiescopyOwnPropertiesFrom(copy,orig);returncopy;}
The properties are copied from orig to copy via this function:
functioncopyOwnPropertiesFrom(target,source){Object.getOwnPropertyNames(source)// (1).forEach(function(propKey){// (2)vardesc=Object.getOwnPropertyDescriptor(source,propKey);// (3)Object.defineProperty(target,propKey,desc);// (4)});returntarget;};
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:
varproto=Object.defineProperty({},'foo',{value:'a',writable:false});varobj=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:
varobj={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 extensibleYou 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: fooYou check whether an object is sealed via:
Object.isSealed(obj)
Freezing is performed via:
Object.freeze(obj)
varpoint={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 extensibleYou 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:
varobj={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):
varPersonProto={describe:function(){return'Person named '+this.name;}};varjane={[[Prototype]]:PersonProto,name:'Jane'};vartarzan={[[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:
functionPerson(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:
functionnewOperator(Constr,args){varthisValue=Object.create(Constr.prototype);// (1)varresult=Constr.apply(thisValue,args);if(typeofresult==='object'&&result!==null){returnresult;// (2)}returnthisValue;}
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
trueIn 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
trueUsually 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
trueThus, 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
trueBecause 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){caseSyntaxError:...break;caseCustomError:...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:
functionConstr(){}varx=newConstr();vary=newx.constructor();console.log(yinstanceofConstr);// 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(){returnnewthis.constructor(...);};
Some inheritance libraries assign the superprototype to a property of a subconstructor. For example, the YUI framework provides subclassing via Y.extend:
functionSuper(){}functionSub(){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
trueYou 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:
valueinstanceofConstr
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:
valueinstanceofConstrConstr.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
trueAs 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(myvarinstanceofArray)...// 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>functiontest(arr){variframe=frames[0];console.log(arrinstanceofArray);// falseconsole.log(arrinstanceofiframe.Array);// trueconsole.log(Array.isArray(arr));// true}</script></head><body><iframesrcdoc="<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:
functionSloppyColor(name){this.name=name;}varc=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:
functionStrictColor(name){'use strict';this.name=name;}varc=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:
classExpression{// Static factory method:publicstaticExpressionparse(Stringstr){if(...){returnnewAddition(...);}elseif(...){returnnewMultiplication(...);}else{thrownewExpressionException(...);}}}...Expressionexpr=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:
functionExpression(str){if(...){returnnewAddition(..);}elseif(...){returnnewMultiplication(...);}else{thrownewExpressionException(...);}}...varexpr=newExpression(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*/functionNames(data){if(data){// There is a parameter// => create instance propertythis.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:
functionNames(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:
functionNames(data){if(data)this.data=data;}Names.prototype={constructor:Names,// (1)getdata(){// Define, don’t assign// => avoid calling the (nonexistent) setterObject.defineProperty(this,'data',{value:[],enumerable:true,configurable:false,writable:false});returnthis.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:
functionFoo(){}Foo.prototype.FACTOR=42;Foo.prototype.compute=function(x){returnx*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 modulefunctionFoo(){}varFACTOR=42;Foo.prototype.compute=function(x){returnx*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:
functionConstrA(){}ConstrA.prototype.TYPE_NAME='ConstrA';functionConstrB(){}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):
functionConstr(...){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:
functionConstr(...){...varthat=this;// make accessible to private functionsvarprivateData=...;functionprivateFunction(...){// Access everythingprivateData=...;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):
functionConstr(...){...this.privilegedMethod=function(...){// Access everythingprivateData=...;privateFunction(...);this.publicData=...;this.publicMethod(...);};}
The following is an implementation of a StringBuilder, using the Crockford privacy pattern:
functionStringBuilder(){varbuffer=[];this.add=function(str){buffer.push(str);};this.toString=function(){returnbuffer.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:
functionStringBuilder(){this._buffer=[];}StringBuilder.prototype={constructor:StringBuilder,add:function(str){this._buffer.push(str);},toString:function(){returnthis._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:
varKEY_BUFFER='_StringBuilder_buffer';
We now access the private data via this[KEY_BUFFER]:
varStringBuilder=function(){varKEY_BUFFER='_StringBuilder_buffer';functionStringBuilder(){this[KEY_BUFFER]=[];}StringBuilder.prototype={constructor:StringBuilder,add:function(str){this[KEY_BUFFER].push(str);},toString:function(){returnthis[KEY_BUFFER].join('');}};returnStringBuilder;}();
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:
varKEY_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:
varobj=function(){// open IIFE// publicvarself={publicMethod:function(...){privateData=...;privateFunction(...);},publicData:...};// privatevarprivateData=...;functionprivateFunction(...){privateData=...;self.publicData=...;self.publicMethod(...);}returnself;}();// 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:
varStringBuilder=function(){// open IIFEvarKEY_BUFFER='_StringBuilder_buffer_'+uuid.v4();functionStringBuilder(){this[KEY_BUFFER]=[];}StringBuilder.prototype={// Omitted: methods accessing this[KEY_BUFFER]};returnStringBuilder;}();// 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:
varobj={method:function(){// open IIFE// method-private datavarinvocCount=0;returnfunction(){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:
functionSub(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):
subInstanceinstanceofSubSub.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:
subInstanceinstanceofSuperSuper.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){varsuperResult=Super.prototype.methodB.call(this,x,y);// (1)returnthis.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:
functionSub(prop1,prop2,prop3,prop4){Sub._super.constructor.call(this,prop1,prop2);this.prop3=prop3;this.prop4=prop4;}Sub.prototype.methodB=function(x,y){varsuperResult=Sub._super.methodB.call(this,x,y);returnthis.prop3+' '+superResult;}
Setting up Sub._super is usually handled by a utility function that also connects the subprototype to the superprototype. For example:
functionsubclasses(SubC,SuperC){varsubProto=Object.create(SuperC.prototype);// Save `constructor` and, possibly, other methodscopyOwnPropertiesFrom(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:
functionPerson(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:
functionEmployee(name,title){Person.call(this,name);this.title=title;}Employee.prototype=Object.create(Person.prototype);Employee.prototype.constructor=Employee;Employee.prototype.describe=function(){returnPerson.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
trueThe utility function subclasses() from the previous section makes the code of Employee slightly simpler and avoids hardcoding the superconstructor Person:
functionEmployee(name,title){Employee._super.constructor.call(this,name);this.title=title;}Employee.prototype.describe=function(){returnEmployee._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=newSuper();// 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/)
trueThe 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
truevalueOf 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 } }
15toString() 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)
falseObject.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')
trueObject.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')
falseSometimes 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():
functionWine(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
54A 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
2But 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
falseStrings, 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:
vararr=Array.prototype.slice.call(arguments);
The method slice() (see Concatenating, Slicing, Joining (Nondestructive)) without any arguments creates a copy of an array-like receiver:
varcopy=['a','b'].slice();
To iterate over all elements of an array-like object, you can use a simple for loop:
functionlogArgs(){for(vari=0;i<arguments.length;i++){console.log(i+'. '+arguments[i]);}}
But you can also borrow Array.prototype.forEach():
functionlogArgs(){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. worldThe 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:
varproto={protoProp:'a'};varobj=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
falseWhat 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:
functiongetOwnProperty(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')
123However, 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 functionYou can fix this problem by directly referring to hasOwnProperty(). This avoids going through obj to find it:
functiongetOwnProperty(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:
functionget(obj,key){returnobj[escapeKey(key)];}functionset(obj,key,value){obj[escapeKey(key)]=value;}// Similar: checking if key exists, deleting an entryfunctionescapeKey(key){if(key.indexOf('__proto__')===0){// (1)returnkey+'%';}else{returnkey;}}
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:
vardict=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)
nullPrototype-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 valueBut 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):
varjane={name:'Jane','not an identifier':123,describe:function(){// methodreturn'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.propKeyobj.propKey=valuedeleteobj.propKey
Bracket operator ([]) (see Bracket Operator ([]): Accessing Properties via Computed Keys):
obj['propKey']obj['propKey']=valuedeleteobj['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)propKeyinobj
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)