Read this article in: Russian, French.
Introduction
This is the second part of article about object-oriented programming in ECMAScript. In the first part we discussed the general theory and drew parallels with ECMAScript. Before reading of the current part, if it is necessary, I recommend reading the first part as in this article we will actively use the passed terminology. You can find the first part here: ECMA-262-3 in detail. Chapter 7.1. OOP: The general theory.
ECMAScript OOP implementation
Having passed the way of highlights of the general theory, we at last have reached the ECMAScript itself. Now, when we know its OOP approach, let’s make once again an accurate definition:
ECMAScript is an object-oriented programming language supporting delegating inheritance based on prototypes.
We begin the analysis from consideration of data types. And first it is necessary to notice that ECMAScript distinguishes entities on primitive values and objects. Therefore the phrase “everything in JavaScript is an object” sometimes arising in various articles, is not correct (is not full). Primitive values concern to a data of certain types which we should discuss in detail.
Data types
Though ECMAScript is a dynamic, weakly typed language with “duck” typing, and automatic type conversion, it nevertheless has certain data types. That is, at one moment, an object belongs to one concrete type.
Standard defines nine types, and only six are directly accessible in an ECMAScript program:
- Undefined
- Null
- Boolean
- String
- Number
- Object
Other three types are accessible only at implementation level (none of ECMAScript objects can have such type) and used by the specification for explaining behavior of some operations, for storing intermediate values and other. These are following types:
- Reference
- List
- Completion
Thus (in short overview), Reference
type is used for an explanation of such operators as delete
, typeof
, this
and other, and consists of a base object and a property name. List
type describes behavior of the arguments list (in the new
expression and function calls). Completion
type in turn is used for an explanation of behavior break
, continue
, return
and throw
statements.
Primitive value types
Coming back to the six types used by ECMAScript programs, first five of them: Undefined
, Null
, Boolean
, String
and Number
are types of primitive values.
Examples of primitive values:
var a = undefined; var b = null; var c = true; var d = 'test'; var e = 10;
These values are represented in implementations directly on a low level. They are not objects, they do not have neither prototypes, nor constructors.
The typeof
operator can be unintuitive if not properly understood. And one such example of that is with the value null
. When null
is supplied to the typeof
operator, the result is "object"
regardless of the fact that the type of null
is specified as Null
.
console.log(typeof null); // "object"
And the reason is that the typeof
operator returns the value taken from standard table which simply says: “for null
value string "object"
should be returned”.
Specification doesn’t clarify this, however Brendan Eich (JavaScript inventor) noticed that null
in contrast with undefined
, is used in mostly where objects appear, i.e. is an essence closely related to objects (meaning the “empty” reference to an object, probably reserved a place for the future purposes). But, in some drafts, there was provided the document where this “phenomenon” was described as a usual bug. Also, this bug appeared in one of bug-trackers where Brendan Eich also participated; as a result it has been decided to leave typeof null
as is, i.e. "object"
though ECMA-262-3 standard defines type of null
as Null
.
Object type
In turn, the Object
type (do not confuse with the Object
constructor, we’re talking now only about abstract types!) is the only type that represents ECMAScript objects.
Object is an unordered collection of key-value pairs.
The keys of objects are called properties. Properties are containers for primitive values and other objects. In case when properties contain functions as their values, they are called methods.
Example:
var x = { // object "x" with three properties: a, b, c a: 10, // primitive value b: {z: 100}, // object "b" with property z c: function () { // function (method) console.log('method x.c'); } }; console.log(x.a); // 10 console.log(x.b); // [object Object] console.log(x.b.z); // 100 x.c(); // 'method x.c'
Dynamic nature
As we noted in chapter 7.1, objects in ES are fully dynamic. It means that we may add, modify or remove properties of objects at any time of program execution.
For example:
var foo = {x: 10}; // add new property foo.y = 20; console.log(foo); // {x: 10, y: 20} // change property value to function foo.x = function () { console.log('foo.x'); }; foo.x(); // 'foo.x' // delete property delete foo.x; console.log(foo); // {y: 20}
Some properties cannot be modified — read-only properties or deleted — non-configurable properties. We’ll consider these cases shortly in the section of property attributes.
Object.freeze(o)
method.
var foo = {x: 10}; // freeze the object Object.freeze(foo); console.log(Object.isFrozen(foo)); // true // can't modify foo.x = 100; // can't extend foo.y = 200; // can't delete delete foo.x; console.log(foo); // {x: 10}
Also it’s possible just to prevent extensions using Object.preventExtensions(o)
method, and to control specific attributes with Object.defineProperty(o)
method:
var foo = {x : 10}; Object.defineProperty(foo, "y", { value: 20, writable: false, // read-only configurable: false // non-configurable }); // can't modify foo.y = 200; // can't delete delete foo.y; // false // prevent extensions Object.preventExtensions(foo); console.log(Object.isExtensible(foo)); // false // can't add new properties foo.z = 30; console.log(foo); {x: 10, y: 20}
For details see this chapter.
Built-in, native and host objects
It is necessary to notice also that the specification distinguishes native objects, built-in objects and host objects.
Built-in and native objects are defined by the ECMAScript specification and the implementation, and a difference between them insignificant. Native objects are the all objects provided by ECMAScript implementation (some of them can be built-in, some can be created during the program execution, for example user-defined objects).
The built-in objects are a subtype of native objects which are built into the ECMAScript prior to the beginning of a program (for example, parseInt
, Math
etc.).
All host objects are objects provided by the host environment, typically a browser, and may include, for example, window
, console.log
, etc.
Notice, that host objects may be implemented using ES itself and completely correspond to the specification’s semantics. From this viewpoint, they can be named (unofficially) as “native-host” objects, though it’s mostly a theoretical aspect. The specification however does not define any “native-host” concept.
Boolean, String and Number objects
Also for some primitives the specification defines special wrapper objects. These are following objects:
- Boolean-object
- String-object
- Number-object
Such objects are created with corresponding built in constructors and contain primitive value as one of internal properties. Object representation can be converted into primitive values and vice-versa.
Examples of the object values corresponding to primitive types:
var c = new Boolean(true); var d = new String('test'); var e = new Number(10); // converting to primitive // conversion: ToPrimitive // applying as a function, without "new" keyword с = Boolean(c); d = String(d); e = Number(e); // back to Object // conversion: ToObject с = Object(c); d = Object(d); e = Object(e);
Besides, there are also objects created by special built in constructors: Function
(function objects constructor) Array
(arrays constructor), RegExp
(regular expressions constructor), Math
(the mathematical module), Date
(the constructor of dates), etc. Such objects are also values of type Object
and their distinction from each other is managed by internal properties which we will discuss below.
Literal notations
For three object values: object, array and regular expression there are short notations which called accordingly an object initialiser, an array initialiser and a regular expression literal:
// equivalent to new Array(1, 2, 3); // or array = new Array(); // array[0] = 1; // array[1] = 2; // array[2] = 3; var array = [1, 2, 3]; // equivalent to // var object = new Object(); // object.a = 1; // object.b = 2; // object.c = 3; var object = {a: 1, b: 2, c: 3}; // equivalent to new RegExp("^\\d+$", "g") var re = /^\d+$/g;
Notice, that in case of reassigning the name bindings — Object
, Array
or RegExp
to some new objects, the semantics of subsequent using of the literal notations may vary in implementations. For example in the current Rhino implementation or in the old SpiderMonkey 1.7 appropriate literal notation will create an object corresponding to the new value of constructor name. In other implementations (including current Spider/TraceMonkey) semantics of the literal notations is not being changed even if constructor name is rebound to the new object:
var getClass = Object.prototype.toString; Object = Number; var foo = new Object; console.log([foo, getClass.call(foo)]); // 0, "[object Number]" var bar = {}; // in Rhino, SpiderMonkey 1.7 - 0, "[object Number]" // in other: still "[object Object]", "[object Object]" console.log([bar, getClass.call(bar)]); // the same with Array name Array = Number; foo = new Array; console.log([foo, getClass.call(foo)]); // 0, "[object Number]" bar = []; // in Rhino, SpiderMonkey 1.7 - 0, "[object Number]" // in other: still "", "[object Object]" console.log([bar, getClass.call(bar)]); // but for RegExp, semantics of the literal // isn't being changed in all tested implementations RegExp = Number; foo = new RegExp; console.log([foo, getClass.call(foo)]); // 0, "[object Number]" bar = /(?!)/g; console.log([bar, getClass.call(bar)]); // /(?!)/g, "[object RegExp]"
Regular Expression Literal and RegExp Objects
Notice although, that in ES3 the two last cases with regular expressions being equivalent semantically, nevertheless differ. The regexp literal exists only in one instance and is created on parsing stage, while RegExp
constructor creates always a new object. This can cause some issues with e.g. lastIndex
property of regexp objects when regexp test is fail:
for (var k = 0; k < 4; k++) { var re = /ecma/g; console.log(re.lastIndex); // 0, 4, 0, 4 console.log(re.test("ecmascript")); // true, false, true, false } // in contrast with for (var k = 0; k < 4; k++) { var re = new RegExp("ecma", "g"); console.log(re.lastIndex); // 0, 0, 0, 0 console.log(re.test("ecmascript")); // true, true, true, true }
Note, in ES5 this issue has been fixed and regexp literal also always creates a new object.
Associative arrays?
Often in various articles or discussions, JavaScript objects (and usually exactly those which created in declarative form — via the object initialiser — {}
) are called hash-tables or simply — hashes (terms from Ruby or Perl), associative arrays (term from PHP), dictionaries (term from Python) etc.
Using of this terminology is a habit to concrete technology. Really, they are similar enough, and in respect of “key-value” pairs storage completely correspond to the theoretical “associative array” or “hash tables” data structures. Moreover, a hash table abstract data type may be and usually is used at implementation level.
However, although terminology is used to describe a conceptual way of thinking, it is not actually technically correct, regarding ECMAScript. As it has been noted, ECMAScript has only one object type and its “subtypes” in respect of a “key-value” pairs storage do not differ from each other. Therefore, there is no separated special term (hash or other) for that. Because any object regardless its internal properties can store these pairs:
var a = {x: 10}; a['y'] = 20; a.z = 30; var b = new Number(1); b.x = 10; b.y = 20; b['z'] = 30; var c = new Function(''); c.x = 10; c.y = 20; c['z'] = 30; // etc. – with any object "subtype"
Moreover, objects in ECMAScript because of delegation can be nonempty, therefore the term “hash” also can be improper:
Object.prototype.x = 10; var a = {}; // create "empty" "hash" console.log(a["x"]); // 10, but it's not empty console.log(a.toString); // function a["y"] = 20; // add new pair to "hash" console.log(a["y"]); // 20 Object.prototype.y = 20; // and property into the prototype delete a["y"]; // remove console.log(a["y"]); // but key and value are still here – 20
null
. It’s achieved with using the Object.create(null)
method. From this viewpoint such objects are simple hash-tables:
var aHashTable = Object.create(null); console.log(aHashTable.toString); // undefined
Also, some properties can have specific getters/setters, so it can also confuse:
var a = new String("foo"); a['length'] = 10; console.log(a['length']); // 3
However, even if to consider that “hash” could have a “prototype” (as for example, in Ruby or Python — a class to which delegate hash-objects), in ECMAScript this terminology can also be improper because there is no semantic differentiation between kinds of property accessors (i.e. dot and bracket notations).
Also in ECMAScript concept of a “property” semantically is not separated into a “key”, “array index”, “method” or “property”. Here all of them are properties which obey to the common law of reading/writing algorithm with examination of the prototype chain.
In the following example on Ruby we see this distinction in semantics and consequently there such terminology can differ:
a = {} a.class # Hash a.length # 0 # new "key-value" pair a['length'] = 10; # but semantics for the dot notation # remains other and means access # to the "property/method", but not to the "key" a.length # 1 # and the bracket notation # provides access to "keys" of a hash a['length'] # 10 # we can augment dynamically Hash class # with new properties/methods and they via # delegation will be available for already created objects class Hash def z 100 end end # a new "property" is available a.z # 100 # but not a "key" a['z'] # nil
ECMA-262-3 standard does not define concept of “hash” (and similar). However, if theoretical data structure is meant, it is possible to name objects so.
Type conversion
To convert an object into a primitive value the method valueOf
can be used. As we noted, the call of the constructor (for certain types) as a function, i.e. without new
operator performs conversion of object type to a primitive value. For this conversion exactly implicit call of the valueOf
method is used:
var a = new Number(1); var primitiveA = Number(a); // implicit "valueOf" call var alsoPrimitiveA = a.valueOf(); // explicit console.log([ typeof a, // "object" typeof primitiveA, // "number" typeof alsoPrimitiveA // "number" ]);
This method allows objects to participate in various operations, for example, in addition:
var a = new Number(1); var b = new Number(2); console.log(a + b); // 3 // or even so var c = { x: 10, y: 20, valueOf: function () { return this.x + this.y; } }; var d = { x: 30, y: 40, // the same .valueOf // functionality as "с" object has, // borrow it: valueOf: c.valueOf }; console.log(c + d); // 100
The value of the valueOf
method by default (if it is not overridden) can vary depending on object type. For some objects it returns the this
value — for example, Object.prototype.valueOf()
, for others — any calculated value, as e.g. Date.prototype.valueOf()
, which returns the time of a date:
var a = {}; console.log(a.valueOf() === a); // true, "valueOf" returned this value var d = new Date(); console.log(d.valueOf()); // time console.log(d.valueOf() === d.getTime()); // true
Also there is one more primitive representation of an object — a string representation. For this toString
method is responsible, which in some operations is also applied automatically:
var a = { valueOf: function () { return 100; }, toString: function () { return '__test'; } }; // in this operation // toString method is // called automatically console.log(a); // "__test" // but here - the .valueOf() method console.log(a + 10); // 110 // but if there is no // valueOf method, it // will be replaced with the //toString method delete a.valueOf; console.log(a + 10); // "_test10"
The toString
method defined on Object.prototype
has special meaning. It returns the value of the internal [[Class]]
property, which we’ll discuss below.
Along with ToPrimitive
conversion, there is also ToObject
conversion which vice-versa converts the value to the object type.
One of explicit ways to call ToObject
is to use built in Object
constructor as a function (though for some types using of Object
with the new
operator is also possible):
var n = Object(1); // [object Number] var s = Object('test'); // [object String] // also for some types it is // possible to call Object with new operator var b = new Object(true); // [object Boolean] // but applied without arguments, // new Object creates a simple object var o = new Object(); // [object Object] // in case if argument for Object function // is already object value, // it simply returns var a = []; console.log(a === new Object(a)); // true console.log(a === Object(a)); // true
Regarding calls of the built in constructors with the new
and without new
operator, there is no the general rule and it depends on the constructor. For example Array
or Function
constructors produce the same results when are called as a constructor (with new
) and as a simple function (without new
):
var a = Array(1, 2, 3); // [object Array] var b = new Array(1, 2, 3); // [object Array] var c = [1, 2, 3]; // [object Array] var d = Function(''); // [object Function] var e = new Function(''); // [object Function]
There are also explicit and implicit type casting when some operators are applied:
var a = 1; var b = 2; // implicit var c = a + b; // 3, number var d = a + b + '5' // "35", string // explicit var e = '10'; // "10", string var f = +e; // 10, number var g = parseInt(e, 10); // 10, number // etc.
Property attributes
All properties can have a number of attributes:
{ReadOnly}
— attempt to write value to the property is ignored; however, ReadOnly-properties can be changed by host-environment actions, therefore ReadOnly — does not mean “constant value”;{DontEnum}
— the property is not enumerable by afor..in
loop;{DontDelete}
— action of thedelete
operator applied to the property is ignored;{Internal}
— the property is internal, it has no name and is used only on implementation level; such properties are not accessible to the ECMAScript program.
{ReadOnly}
, {DontEnum}
and {DontDelete}
are renamed accordingly into the [[Writable]]
, [[Enumerable]]
and [[Configurable]]
and can be manually managed via the Object.defineProperty
and similar methods.
var foo = {}; Object.defineProperty(foo, "x", { value: 10, writable: true, // aka {ReadOnly} = false enumerable: false, // aka {DontEnum} = true configurable: true // {DontDelete} = false }); console.log(foo.x); // 10 // attributes set is called a descriptor var desc = Object.getOwnPropertyDescriptor(foo, "x"); console.log(desc.enumerable); // false console.log(desc.writable); // true // etc.
Internal properties and methods
Objects also can have a number of internal properties which are a part of implementation and inaccessible for ECMAScript programs directly (however as we will see below, some implementations allow the access to some such properties). These properties by the convention are enclosed with double square brackets — [[ ]]
.
We will touch some of them (obligatory for all objects); description of other properties can be found in the specification.
Each object should implement the following internal properties and methods:
[[Prototype]]
— the prototype of this object (it will be considered below in detail);[[Class]]
— a string representation of object’s kind (for example,Object
,Array
,Function
, etc.); it is used to distinguish the objects;[[Get]]
— a method of getting the property’s value;[[Put]]
— a method of setting the property’s value;[[CanPut]]
— checks whether writing to the property is possible;[[HasProperty]]
— checks whether the object has already this property;[[Delete]]
— removes the property from the object;[[DefaultValue]]
— returns a primitive value corresponding with the object (for getting this value thevalueOf
method is called; for some objects,TypeError
exception can be thrown).
To get the [[Class]]
property from ECMAScript programs is possible indirectly via the Object.prototype.toString()
method. This method should return the following string: "[object " + [[Class]] + "]"
. For example:
var getClass = Object.prototype.toString; getClass.call({}); // [object Object] getClass.call([]); // [object Array] getClass.call(new Number(1)); // [object Number] // etc.
This feature is often used to check the kind of an object, however, it is necessary to note that by the specification internal [[Class]]
property of hosts-objects can be any, including values of the [[Class]]
property of the built in objects, that in theory does not make such checks 100% proved. For example, [[Class]]
property of the document.childNodes.item(...)
method in older IE returns "String"
(in other implementations, "Function"
is returned):
// in older IE - "String", in other - "Function" console.log(getClass.call(document.childNodes.item));
Constructor
So, as we mentioned above, objects in ECMAScript are created via, so-called, constructors.
Constructor is a function that creates and initializes the newly created object.
For creation (memory allocation) the [[Construct]]
internal method of a constructor function is responsible. The behavior of this internal method is specified and all constructor functions uses this method to allocate memory for new object.
And initialization is managed by calling the function in context of newly created object. For this already internal [[Call]]
method of the constructor function is responsible.
Note, that from user-code only the initialization phase is accessible. Though, even from initialization we can return different object ignoring this
object which was created at the first stage:
function A() { // update newly created object this.x = 10; // but return different object return [1, 2, 3]; } var a = new A(); console.log(a.x, a); undefined, [1, 2, 3]
Referencing to algorithm of creation of Function objects discussed in the Chapter 5. Functions, we see that function is a native object which among other properties has this internal [[Construct]]
and [[Call]]
properties and also explicit prototype
property — the reference to a prototype of the future objects (notice, NativeObject
here and below is my pseudo-code naming convention for “native object” concept from ECMA-262-3, but not the built-in constructor).
F = new NativeObject(); F.[[Class]] = "Function" .... // other properties F.[[Call]] = <reference to function> // function itself F.[[Construct]] = internalConstructor // general internal constructor .... // other properties // prototype of objects created by the F constructor __objectPrototype = {}; __objectPrototype.constructor = F // {DontEnum} F.prototype = __objectPrototype
Thus [[Call]]
besides the [[Class]]
property (which equals to "Function"
) is the main in respect of objects distinguishing. Therefore the objects having internal [[Call]]
property are called as functions. The typeof
operator for such objects returns "function"
value. However, it mostly relates to native objects, in case of host callable objects, the typeof
operator (no less than [[Class]]
property) of some implementations can return other value: for example, window.console.log(...)
in IE:
// in IE - "Object", "object", in other - "Function", "function" console.log(Object.prototype.toString.call(window.console.log)); console.log(typeof window.console.log); // "Object"
The internal [[Construct]]
method is activated by the new
operator applied to the constructor function. As we said this method is responsible for memory allocation and creation of the object. If there are no arguments, call parenthesis of constructor function can be omitted:
function A(x) { // constructor А this.x = x || 10; } // without arguments, call // brackets can be omitted var a = new A; // or new A(); console.log(a.x); // 10 // explicit passing of // x argument value var b = new A(20); console.log(b.x); // 20
And as also we know, this value inside the constructor (at initialization phase) is set to the newly created object.
Let’s consider the algorithm of objects creation.
Algorithm of objects creation
The behavior of the internal [[Construct]]
method can be described as follows:
F.[[Construct]](initialParameters): O = new NativeObject(); // property [[Class]] is set to "Object", i.e. simple object O.[[Class]] = "Object" // get the object on which // at the moment references F.prototype var __objectPrototype = F.prototype; // if __objectPrototype is an object, then: O.[[Prototype]] = __objectPrototype // else: O.[[Prototype]] = Object.prototype; // where O.[[Prototype]] is the prototype of the object // initialization of the newly created object // applying the F.[[Call]]; pass: // as this value – newly created object - O, // arguments are the same as initialParameters for F R = F.[[Call]](initialParameters); this === O; // where R is the returned value of the [[Call]] // in JS view it looks like: // R = F.apply(O, initialParameters); // if R is an object return R // else return O
Note two major features:
First, the prototype of the created object is taken from the prototype
property of a function on the current moment (it means that the prototype of two created objects from one constructor can vary since the prototype
property of a function can also vary).
Secondly, as we have mentioned above, if at object initialization the [[Call]]
has returned an object, exactly it is used as the result of the whole new
expression:
function A() {} A.prototype.x = 10; var a = new A(); console.log(a.x); // 10 – by delegation, from the prototype // set .prototype property of the // function to new object; why explicitly // to define the .constructor property, // will be described below A.prototype = { constructor: A, y: 100 }; var b = new A(); // object "b" has new prototype console.log(b.x); // undefined console.log(b.y); // 100 – by delegation, from the prototype // however, prototype of the "a" object // is still old (why - we will see below) console.log(a.x); // 10 - by delegation, from the prototype function B() { this.x = 10; return new Array(); } // if "B" constructor had not return // (or was return this), then this-object // would be used, but in this case – an array var b = new B(); console.log(b.x); // undefined console.log(Object.prototype.toString.call(b)); // [object Array]
Let’s consider a prototype deeply in detail.
Prototype
Every object has a prototype (exceptions can be with some system objects). Communication with a prototype is organized via the internal, implicit and inaccessible directly [[Prototype]]
property. A prototype can be either an object, or the null
value.
Property constructor
In the above example there are two important points. The first relates to constructor
property of the function’s prototype
property.
As we can see in algorithm of function objects creation, constructor
property is set to function’s prototype
property at function creation. The value of this property is the circular reference to the function itself:
function A() {} var a = new A(); console.log(a.constructor); // function A() {}, by delegation console.log(a.constructor === A); // true
Often in this case there is a misunderstanding — constructor
property is incorrectly treated as own property of the created object. However as we have seen, this property belongs to a prototype and is accessible to object via inheritance.
Via the inherited constructor
property instances can indirectly get the reference to the prototype object:
function A() {} A.prototype.x = new Number(10); var a = new A(); console.log(a.constructor.prototype); // [object Object] console.log(a.x); // 10, via delegation // the same as a.[[Prototype]].x console.log(a.constructor.prototype.x); // 10 console.log(a.constructor.prototype.x === a.x); // true
Notice though, that both constructor
and prototype
properties of the function can be redefined after the object is created. In this case the object looses the reference via the mechanism above.
If we add new or modifiy existing property in the original prototype via the function’s prototype
property, instances will see the newly added properties.
However, if we change function’s prototype
property completely (via assigning a new object), the reference to the original constructor (as well as to the original prototype) is lost. This is because we create the new object which does not have constructor
property:
function A() {} A.prototype = { x: 10 }; var a = new A(); console.log(a.x); // 10 console.log(a.constructor === A); // false!
Therefore, this reference should be restored manually:
function A() {} A.prototype = { constructor: A, x: 10 }; var a = new A(); console.log(a.x); // 10 console.log(a.constructor === A); // true
Notice though that the restored manually constructor
property, in contrast with the lost original, has no attribute {DontEnum}
and, as consequence, is enumerable in the for..in
loop over the A.prototype
.
[[Enumerable]]
attribute.
var foo = {x: 10}; Object.defineProperty(foo, "y", { value: 20, enumerable: false // aka {DontEnum} = true }); console.log(foo.x, foo.y); // 10, 20 for (var k in foo) { console.log(k); // only "x" } var xDesc = Object.getOwnPropertyDescriptor(foo, "x"); var yDesc = Object.getOwnPropertyDescriptor(foo, "y"); console.log( xDesc.enumerable, // true yDesc.enumerable // false );
Explicit prototype
and implicit [[Prototype]]
properties
Often prototype of an object is incorrectly confused with explicit reference to the prototype via the function’s prototype
property. Yes, really, it references to the same object, as object’s [[Prototype]]
property:
a.[[Prototype]] ----> Prototype <---- A.prototype
Moreover, [[Prototype]]
of an instance gets its value from exactly the prototype
property of the constructor — at object’s creation.
However, replacing prototype
property of the constructor does not affect the prototype of already created objects. It’s only the prototype
property of the constructor that is changed! It means that new objects will have a new prototype. But already created objects (before the prototype
property was changed), have reference to the old prototype and this reference cannot be changed already:
// was before changing of A.prototype a.[[Prototype]] ----> Prototype <---- A.prototype // became after A.prototype ----> New prototype // new objects will have this prototype a.[[Prototype]] ----> Prototype // reference to old prototype
Example:
function A() {} A.prototype.x = 10; var a = new A(); console.log(a.x); // 10 A.prototype = { constructor: A, x: 20 y: 30 }; // object "а" delegates to // the old prototype via // implicit [[Prototype]] reference console.log(a.x); // 10 console.log(a.y) // undefined var b = new A(); // but new objects at creation // get reference to new prototype console.log(b.x); // 20 console.log(b.y) // 30
Therefore, sometimes arising statements in articles on JavaScript claiming that “dynamic changing of the prototype will affect all objects and they will have that new prototype” is incorrect. New prototype will have only new objects which will be created after this changing.
The main rule here is: the object’s prototype is set at the moment of object’s creation and after that cannot be changed to new object. Using the explicit prototype
reference from the constructor if it still refers to the same object, it is possible only to add new or modify existing properties of the object’s prototype.
Non-standard __proto__
property
However, some implementations, for example, SpiderMonkey, provide explicit reference to object’s prototype via the non-standard __proto__
property:
function A() {} A.prototype.x = 10; var a = new A(); console.log(a.x); // 10 var __newPrototype = { constructor: A, x: 20, y: 30 }; // reference to new object A.prototype = __newPrototype; var b = new A(); console.log(b.x); // 20 console.log(b.y); // 30 // "a" object still delegates // to the old prototype console.log(a.x); // 10 console.log(a.y); // undefined // change prototype explicitly a.__proto__ = __newPrototype; // now "а" object references // to new object also console.log(a.x); // 20 console.log(a.y); // 30
Object.getPrototypeOf(O)
method, which directly returns the [[Prototype]]
property of an object — the original prototype of the instance. However, in contrast with __proto__
, being only a getter, it does not allow to set the prototype.
var foo = {}; Object.getPrototypeOf(foo) == Object.prototype; // true
Object is independent from its constructor
Since the prototype of an instance is independent from the constructor and the prototype
property of the constructor, the constructor after its main purpose — creation of the object — can be removed. The prototype object will continue to exist, being referenced via the [[Prototype]]
property:
function A() {} A.prototype.x = 10; var a = new A(); console.log(a.x); // 10 // set "А" to null - explicit // reference on constructor A = null; // but, still possible to create // objects via indirect reference // from other object if // .constructor property has not been changed var b = new a.constructor(); console.log(b.x); // 10 // remove implicit reference // after it `a.constructor`, and `b.constructor` // will point to the default Object function, but not `A` delete a.constructor.prototype.constructor; // it is not possible to create objects // of "А" constructor anymore, but still // there are two such objects which // still have reference to their prototype console.log(a.x); // 10 console.log(b.x); // 10
Feature of instanceof
operator
With the explicit reference to a prototype — via the prototype
property of the constructor, the work of the instanceof
operator is related.
This operator works exactly with the prototype chain of an object but not with the constructor itself. Take this into account, since there is often misunderstanding at this place. That is, when there is a check:
if (foo instanceof Foo) { ... }
it does not mean the check whether the object foo
is created by the Foo
constructor!
All the instanceof
operator does is only takes the value of the Foo.prototype
property and checks its presence in the prototype chain of foo
, starting from the foo.[[Prototype]]
. The instanceof
operator is activated by the internal [[HasInstance]] method of the constructor.
Let’s see it on the example:
function A() {} A.prototype.x = 10; var a = new A(); console.log(a.x); // 10 console.log(a instanceof A); // true // if set A.prototype // to null... A.prototype = null; // ...then "a" object still // has access to its // prototype - via a.[[Prototype]] console.log(a.x); // 10 // however, instanceof operator // can't work anymore, because // starts its examination from the //prototype property of the constructor console.log(a instanceof A); // error, A.prototype is not an object
On the other hand, it is possible to create object by one constructor, but instanceof
will return true
on check with another constructor. All that is necessary is to set object’s [[Prototype]]
property and prototype
property of the constructor to the same object:
function B() {} var b = new B(); console.log(b instanceof B); // true function C() {} var __proto = { constructor: C }; C.prototype = __proto; b.__proto__ = __proto; console.log(b instanceof C); // true console.log(b instanceof B); // false
Prototype as a storage for methods and shared properties
The most useful application of the prototype in ECMAScript is the storage of methods, default state and shared properties of objects.
Indeed, objects can have their own states, but methods are usually the same. Therefore, methods, for optimization of a memory usage, are usually defined in the prototype. It means that all instances created by this constructor, always share the same method.
function A(x) { this.x = x || 100; } A.prototype = (function () { // initializing context, // use additional object var _someSharedVar = 500; function _someHelper() { console.log('internal helper: ' + _someSharedVar); } function method1() { console.log('method1: ' + this.x); } function method2() { console.log('method2: ' + this.x); _someHelper(); } // the prototype itself return { constructor: A, method1: method1, method2: method2 }; })(); var a = new A(10); var b = new A(20); a.method1(); // method1: 10 a.method2(); // method2: 10, internal helper: 500 b.method1(); // method1: 20 b.method2(); // method2: 20, internal helper: 500 // both objects are use // the same methods from // the same prototype console.log(a.method1 === b.method1); // true console.log(a.method2 === b.method2); // true
Reading and writing properties
As we mentioned, reading and writing of properties are managed by the internal methods [[Get]]
and [[Put]]
. The methods are activated by property accessors — dot notation or brackets notation:
// write foo.bar = 10; // [[Put]] is called console.log(foo.bar); // 10, [[Get]] is called console.log(foo['bar']); // the same
Let’s show the work of these methods as a pseudo-code.
[[Get]]
method
The [[Get]]
method considers the properties from the prototype chain of object as well. Therefore properties of a prototype are accessible to object as own.
O.[[Get]](P): // if there is own // property, return it if (O.hasOwnProperty(P)) { return O.P; } // else, analyzing prototype var __proto = O.[[Prototype]]; // if there is no prototype (it is, // possible e.g. in the last link of the // chain - Object.prototype.[[Prototype]], // which is equal to null), // then return undefined; if (__proto === null) { return undefined; } // else, call [[Get]] method recursively - // now for prototype; i.e. go through prototype // chain: try to find property in the // prototype, after that – in a prototype of // the prototype and so on, until // [[Prorotype]] will be equal to null return __proto.[[Get]](P)
Note, since the [[Get]]
method in one of cases can return undefined
, checks on variable presence, like the following are possible:
if (window.someObject) { ... }
Here, property someObject
is not found in window
, then in its prototype, in the prototype of the prototype etc., and in this case, by the algorithm, undefined
value is returned.
Notice, that for exactly presence the in
operator is responsible. It also considers the prototype chain:
if ('someObject' in window) { ... }
It helps to avoid cases when, for example, someObject
can be equal to false
and the first check does not pass even if someObject
exists.
[[Put]]
method
The [[Put]]
method in contrast creates or updates an own property of the object and shadows the property with the same name from the prototype.
O.[[Put]](P, V): // if we can't write to // this property then exit if (!O.[[CanPut]](P)) { return; } // if object doesn't have such own, // property, then create it; all attributes // are empty (set to false) if (!O.hasOwnProperty(P)) { createNewProperty(O, P, attributes: { ReadOnly: false, DontEnum: false, DontDelete: false, Internal: false }); } // set the value; // if property existed, its // attributes are not changed O.P = V return;
For example:
Object.prototype.x = 100; var foo = {}; console.log(foo.x); // 100, inherited foo.x = 10; // [[Put]] console.log(foo.x); // 10, own delete foo.x; console.log(foo.x); // again 100, inherited
[[CanPut]]
internal method; see 8.6.2.3 of ES3.
// For example, property "length" of // string objects is read-only; let's make a // string as a prototype of our object and try // to shadow the "length" property function SuperString() { /* nothing */ } SuperString.prototype = new String("abc"); var foo = new SuperString(); console.log(foo.length); // 3, the length of "abc" // try to shadow foo.length = 5; console.log(foo.length); // still 3
In strict mode of ES5 an attempt to shadow a non-writable property results a TypeError
.
Property accessors
That’s said, internal methods [[Get]]
and [[Put]]
are activated by property accessors which in ECMAScript are available via the dot notation, or via the bracket notation. The dot notation is used when the property name is a valid identifier name and in advance known, bracket notation allows forming names of properties dynamically.
var a = {testProperty: 10}; console.log(a.testProperty); // 10, dot notation console.log(a['testProperty']); // 10, bracket notation var propertyName = 'Property'; console.log(a['test' + propertyName]); // 10, bracket notation with dynamic property
There is one important feature — property accessor always calls ToObject
conversion for the object standing on left hand side from the property accessor. And because of this implicit conversion it is possible roughly speaking to say that “everything in JavaScript is an object” (however as we already know — of course not everything since there are also primitive things).
If we use property accessor with a primitive value, we just create intermediate wrapper object with corresponding value. After the work is finished, this wrapper is removed.
Example:
var a = 10; // primitive value // but, it has access to methods, // just like it would be an object console.log(a.toString()); // "10" // moreover, we can even // (try) to create a new // property in the "а" primitive calling [[Put]] a.test = 100; // seems, it even works // but, [[Get]] doesn't return // value for this property, it returns // by algorithm - undefined console.log(a.test); // undefined
So, why in this example “primitive” value a
has access to the toString
method, but has no to the newly created test
property?
The answer is simple:
First, as we said, after the property accessor is applied, it is already not a primitive, but the intermediate object. In this case new Number(a) is used, which via delegation finds the toString
method in the prototype chain:
// Algorithm of evaluating a.toString(): 1. wrapper = new Number(a); 2. wrapper.toString(); // "10" 3. delete wrapper;
Next, [[Put]]
method also creates its own wrapper object when evaluating the test
property:
// Algorithm of evaluating a.test = 100: 1. wrapper = new Number(a); 2. wrapper.test = 100; 3. delete wrapper;
We see that in step 3 the wrapper is removed and its newly created test
property is of course also — with removing the object itself.
Then again [[Get]]
is called where the property accessor creates again new wrapper which of course does not known anything about any test
property:
// Algorithm of evaluating a.test: 1. wrapper = new Number(a); 2. wrapper.test; // undefined
That is the reference to properties/methods from a primitive value makes sense only for reading the properties. Also if any of primitive values often uses the access to properties, for economy of time resources, there is a sense directly to replace it with an object representation. And on the contrary — if values participate only in some small calculations which are not demanding the access to properties then more efficiently primitive values can be used.
Inheritance
As we know, ECMAScript uses delegating inheritance based on prototypes.
Chaining, prototypes generate already mentioned prototype chain.
Actually, all work for implementing delegation and the analysis of a prototype chain is reduced to the work of the mentioned above [[Get]]
method.
If you completely understand the simple algorithm of the [[Get]]
method, the question on inheritance in JavaScript will disappear by itself and the answer to it will become clear.
Often on forums when the talk comes about inheritance in JavaScript, I show as an example only one line of ECMAScript code which very exactly and accurate describes object structure of the language and shows delegation based inheritance. Indeed, we can do not create any constructors or objects but the whole language is already full of inheritance. The line of code is very simple:
console.log(1..toString()); // "1"
Now, when we know the algorithm of the [[Get]]
method and property accessors, we can see what happens here:
1. First, from a primitive value 1
the wrapper object as new Number(1)
is created;
2. Then the inherited method toString
is called from this wrapper.
Why the inherited? Because objects in ECMAScript can have own properties, and the created wrapper object, in this case, has no own toString
method. Therefore, it inherits it from a prototype, i.e. Number.prototype
.
1.toString(); // SyntaxError! (1).toString(); // OK 1 .toString(); // OK (space after 1) 1..toString(); // OK 1['toString'](); // OK
Prototype chain
Let’s show how to create these prototype chains for the user-defined objects. It is quite simple:
function A() { console.log('A.[[Call]] activated'); this.x = 10; } A.prototype.y = 20; var a = new A(); console.log([a.x, a.y]); // 10 (own), 20 (inherited) function B() {} // the easiest variant of prototypes // chaining is setting child // prototype to new object created, // by the parent constructor B.prototype = new A(); // fix .constructor property, else it would be А B.prototype.constructor = B; var b = new B(); console.log([b.x, b.y]); // 10, 20, both are inherited // [[Get]] b.x: // b.x (no) --> // b.[[Prototype]].x (yes) - 10 // [[Get]] b.y // b.y (no) --> // b.[[Prototype]].y (no) --> // b.[[Prototype]].[[Prototype]].y (yes) - 20 // where b.[[Prototype]] === B.prototype, // and b.[[Prototype]].[[Prototype]] === A.prototype
This approach has two features.
First, B.prototype
will contain x
property. At first glance, seems that it is not correct, since x
property is defined in A
as own and is expected to be own as well in objects of the B
constructor.
In a case of prototypal inheritance though it is normal situation, since the descendant object, if has no such own property delegates to a prototype. The idea behind this is that probably, objects created by the B
constructor do not need x
property. In contrast, in the class based model, all properties are copied to the class-descendant.
However, if nevertheless it is needed (emulating class-based approach) that x
property be own for the objects created by B
constructor, there are some techniques for this, one of which we will show below.
Secondly, that is already not a feature but the disadvantage — the code of the constructor is also executed when the descendant prototype is created. We can see that the message "A.[[Call]] activated"
is shown twice — when the object created by the A
constructor is used for B.prototype
and also at creation of object a
object itself!
A more critical example is a thrown exception in the parent constructor: perhaps, for the real objects created by this constructor such checks are needed, but obviously, the same case is completely unacceptable with using these parent objects as prototypes:
function A(param) { if (!param) { throw 'Param required'; } this.param = param; } A.prototype.x = 10; var a = new A(20); console.log([a.x, a.param]); // 10, 20 function B() {} B.prototype = new A(); // Error
Besides, heavy calculations in the parent constructor can also be considered as disadvantage of this approach.
To solve these “features” and issues, today programmers use standard pattern for chaining the prototypes, which we show below. The main goal of this trick consists in creation of the intermediate wrapper constructor which chains the needed prototypes.
function A() { console.log('A.[[Call]] activated'); this.x = 10; } A.prototype.y = 20; var a = new A(); console.log([a.x, a.y]); // 10 (own), 20 (inherited) function B() { // or simply A.apply(this, arguments) B.superproto.constructor.apply(this, arguments); } // inheritance: chaining prototypes // via creating empty intermediate constructor var F = function () {}; F.prototype = A.prototype; // reference B.prototype = new F(); B.superproto = A.prototype; // explicit reference to ancestor prototype, "sugar" // fix .constructor property, else it would be A B.prototype.constructor = B; var b = new B(); console.log([b.x, b.y]); // 10 (own), 20 (inherited)
Notice how we create own property x
on b
instance: we call parent constructor via the B.superproto.constructor
reference in context of newly created object.
We have fixed also the issue with non-needed call of the parent constructor for creating the descendant prototype. Now the message "A.[[Call]] activated"
is shown when is needed.
And for not to repeat every time the same actions of prototypes chaining (creation of the intermediate constructor, setting this superproto
sugar, restoring the original constructor
etc.), this template can be encapsulated in the convenient util function, which purpose is to chain prototypes regardless the concrete names of constructors:
function inherit(child, parent) { var F = function () {}; F.prototype = parent.prototype child.prototype = new F(); child.prototype.constructor = child; child.superproto = parent.prototype; return child; }
Accordingly, inheritance:
function A() {} A.prototype.x = 10; function B() {} inherit(B, A); // chaining prototypes var b = new B(); console.log(b.x); // 10, found in the A.prototype
There are many variations of such wrappers (in respect of syntax); however, all of them are reduced to the actions described above.
For example, we can optimize the previous wrapper if we will put intermediate constructor outside (thus, only one function will be created), thereby, reusing it:
var inherit = (function(){ function F() {} return function (child, parent) { F.prototype = parent.prototype; child.prototype = new F; child.prototype.constructor = child; child.superproto = parent.prototype; return child; }; })();
Since the real prototype of an object is the [[Prototype]]
property, it means that the F.prototype
can be easily changed and reused, because child.prototype
, being created via new F
, will get its [[Prototype]]
from the the current value of child.prototype
:
function A() {} A.prototype.x = 10; function B() {} inherit(B, A); B.prototype.y = 20; B.prototype.foo = function () { console.log("B#foo"); }; var b = new B(); console.log(b.x); // 10, is found in A.prototype function C() {} inherit(C, B); // and using our "superproto" sugar // we can call parent method with the same name C.prototype.foo = function () { C.superproto.foo.call(this); console.log("C#foo"); }; var c = new C(); console.log([c.x, c.y]); // 10, 20 c.foo(); // B#foo, C#foo
Object.create
method.Simplified version in ES3 can nearly be implemented in the following way:
Object.create || Object.create = function (parent, properties) { function F() {} F.prototype = parent; var child = new F; for (var k in properties) { child[k] = properties[k].value; } return child; }
Usage:
var foo = {x: 10}; var bar = Object.create(foo, {y: {value: 20}}); console.log(bar.x, bar.y); // 10, 20
For details see this chapter.
Also, all existing variations of imitations of “classical inheritance in JS” are based on this principle. Now we see, that in fact it is even not an “imitation of class based inheritance”, but simply a convenient code reuse for chaining prototypes.
// ES6 class Foo { constructor(name) { this._name = name; } getName() { return this._name; } } class Bar extends Foo { getName() { return super.getName() + ' Doe'; } } var bar = new Bar('John'); console.log(bar.getName()); // John Doe
Conclusion
This article has turned out big enough and detailed. I hope that its material is useful and has dispelled some doubts regarding ECMAScript. If you have any questions or additions, they as always can be discussed in comments.
Additional literature
- 4.2 — Language Overview;
- 4.3 — Definitions;
- 7.8.5 — Regular Expression Literals;
- 8 — Types;
- 9 — Type Conversion;
- 11.1.4 — Array Initialiser;
- 11.1.5 — Object Initialiser;
- 11.2.2 — The new Operator;
- 13.2.1 — [[Call]];
- 13.2.2 — [[Construct]];
- 15 — Native ECMAScript Objects.
Translated by: Dmitry Soshnikov with additions by Garrett Smith.
Published on: 2010-03-04
Originally written by: Dmitry Soshnikov [ru, read »]
Originally published on: 2009-09-12 [ru]
“console.log(a.constructor.prototype); // [object Object]”
Really is
console.log(a.constructor.prototype); // A { x: [Number: 10] }
In the example for the section “Object is independent from its constructor” there are two delete statements:
I believe the second statement
a) is unnecessary (for the purpose of this specific example)
b) does not do what was probably expected (according to the comment to the code)
Reason:
Since both
a.[[Prototype]]
andb.[[Prototype]]
initially point to the sameA.prototype
object then the first delete statement already breaks the link that objectb
could use to point to its initial constructorA
viab.[[Prototype]].constructor
since their common[[Prototype]].constructor
has gone.What is not obvious is that after a.prototype.constructor property is deleted, both a.prototype.constructor and
b.prototype.constructor
accessors return not null but “default constructor” instead. That isObject
constructor function.So the second delete statement actually deletes
Object.prototype.constructor
property.One can see the actual results with the slightly modified code (shown as the comments):
@Alexey Gorkov, correct, and a very good catch! Thanks, I fixed it.
// fix .constructor property, else it would be А
B.prototype.constructor = B;
Could you please elaborate on..
What do you mean by above and how that make a difference in first most example ?
@KRISHNA CHANDU AKULA, the
constructor
property by inheritance chain would still findA
:This happens because
B.prototype
is assigned an instance ofA
. This instance ofA
inherits fromA.prototype
, where eventually theconstructor
property is found:Very good article, thank you very much!
I am about to write one myself, although it will be much shorter 🙂
I read some articles in chinese, some of them say that user-defined object is another object type, the others say that user-defined object belongs to host object. But, you say user-defined object belongs to native object; it’s hard for me to distinguih them.
@rookieLink
A native object is the one which semantics is fully defined by the ECMAScript spec.
A host object is the one provided by the host, i.e. the environment where ECMAScript engine is used in. Examples:
alert
function in Browser is a host object, since it’s not defined in the spec.A built-in object is a native object, which is defined prior program execution by the engine itself.
Hello. Thanks for great articles 🙂
I have a question about make intermediate function. We can do same thing without intermediate function for example like this. What is difference between intermediate wrapper and my example?
@Murad Sofiyev, thanks for the feedback. Yes, this would work too, as long as you have access to the
__proto__
directly. It’s just recently standardized (even though existed for years in many implementations), but direct usage of it is not recommended.