JavaScript has an interesting inheritance mechanism: prototypal. Most of the starting JavaScript developers have hard time understanding it, as well as I had.
All types in JavaScript (except the null
and undefined
values) have a constructor property, which is a part of the inheritance. For example:
var num = 150;num.constructor === Number // => truevar obj = {};obj.constructor === Object // => truevar reg = /\d/g;reg.constructor === RegExp; // => true
In this article we'll dive into the constructor
property of an object. It serves as a public identity of the class, which means it can be used for:
- Identify to what class belongs an object (an alternative to
instanceof
) - Reference from an object or a prototype the constructor function
- Get the class name
1. The constructor in primitive types
In JavaScript the primitive types are number, boolean, string, symbol (in ES6), null
and undefined
.
Any value except null
and undefined
has a constructor
property, which refers to the corresponding type function:
Number()
for numbers:(1).constructor === Number
Boolean()
for booleans:(true).constructor === Boolean
String()
for strings:('hello').constructor === String
Symbol()
for symbols:Symbol().constructor === Symbol
The constructor property of a primitive can be used to determine it's type by comparing it with the corresponding function. For example to verify if the value is a number:
if (myVariable.constructor === Number) { // code executed when myVariable is a number myVariable += 1;}
Notice that this approach is generally not recommended and typeof
is preferable (see 1.1). But it can be useful for a switch
statement, to reduce the number of if/else
:
// myVariable = ...var type;switch (myVariable.constructor) { case Number: type = 'number'; break; case Boolean: type = 'boolean'; break; case String: type = 'string'; break; case Symbol: type = 'symbol'; break; default: type = 'unknown'; break;}
1.1 The object wrapper for a primitive value
An object wrapper for a primitive is created when invoking the function with new
operator. Wrappers can be created for new String('str')
, new Number(15)
and new Boolean(false)
. It cannot be created for a Symbol
, because invoked this way new Symbol('symbol')
generates a TypeError.
The wrapper exists to allow developer to attach custom properties and methods to a primitive, because JavaScript doesn't allow for primitives to have own properties.
Existence of these objects may create confusion for determining the variable type based on the constructor, because the wrapper has the same constructor as the primitive:
var booleanObject = new Boolean(false);booleanObject.constructor === Boolean // => truevar booleanPrimitive = false;booleanPrimitive.constructor === Boolean // => true
2. The constructor in a prototype object
The constructor
property in a prototype is automatically setup to reference the constructor function.
function Cat(name) { this.name = name;}Cat.prototype.getName = function() { return this.name;}Cat.prototype.clone = function() { return new this.constructor(this.name);}Cat.prototype.constructor === Cat // => true
Because properties are inherited from the prototype, the constructor
is available on the instance object too.
var catInstance = new Cat('Mew');catInstance.constructor === Cat // => true
Even if the object is created from a literal, it inherits the constructor from Object.prototype
.
var simpleObject = { weekDay: 'Sunday'};simpleObject.prototype === Object // => true
2.1 Don't loose the constructor in the subclass
constructor
is a regular non-enumerable property in the prototype object. It does not update automatically when a new object is created based on it. When creating a subclass, the correct constructor should be setup manually.
The following example creates a sublcass Tiger
of the Cat
superclass. Notice that initially Tiger.prototype
still points to Cat
constructor.
function Tiger(name) { Cat.call(this, name);}Tiger.prototype = Object.create(Cat.prototype);// The prototype has the wrong constructorTiger.prototype.constructor === Cat // => trueTiger.prototype.constructor === Tiger // => false
Now if we clone a Tiger
instance using clone()
method defined on Cat.prototype
, it will create a wrong Cat
instance.
var tigerInstance = new Tiger('RrrMew');var wrongTigerClone = tigerInstance.clone();tigerInstance instanceof Tiger // => true// Notice that wrongTigerClone is incorrectly a Cat instancewrongTigerClone instanceof Tiger // => falsewrongTigerClone instanceof Cat // => true
It happens because Cat.prototype.clone()
uses new this.constructor()
to create a new clone. But the constructor still points to Cat
function.
To fix this problem it's necessary to manually update the Tiger.prototype
with the correct constructor function: Tiger
. The clone()
method will be fixed too.
//Fix the Tiger prototype constructorTiger.prototype.constructor = Tiger;Tiger.prototype.constructor === Tiger // => truevar tigerInstance = new Tiger('RrrMew');var correctTigerClone = tigerInstance.clone();// Notice that correctTigerClone is correctly a Tiger instancecorrectTigerClone instanceof Tiger // => truecorrectTigerClone instanceof Cat // => true
Check this demo for a complete example.
3. An alternative to instanceof
object instanceof Class
is used to determine if the object
has the same prototype as the Class
.
This operator searches in the prototype chain too, which sometimes makes difficult to identify the subclass instance from superclass instance. For example:
var tigerInstance = new Tiger('RrrMew');tigerInstance instanceof Cat // => truetigerInstance instanceof Tiger // => true
As seen in the example, it's not possible to check if tigerInstance
is exactly a Cat
or Tiger
, because instanceof
returns true
in both cases.
This is where the constructor
property shines, allowing to strictly determine the instance class.
tigerInstance.constructor === Cat // => falsetigerInstance.constructor === Tiger // => true// or using switchvar type;switch (tigerInstance.constructor) { case Cat: type = 'Cat'; break; case Tiger: type = 'Tiger'; break; default: type = 'unknown'; }type // => 'Tiger'
4. Get the class name
The function object in JavaScript has a property name. It returns the name of the function or an empty string for anonymous one.
In addition with constructor
property, this can be useful to determine the class name, as an alternative to Object.prototype.toString.call(objectInstance)
.
var reg = /\d+/;reg.constructor.name // => 'RegExp'Object.prototype.toString.call(reg) // => '[object RegExp]'var myCat = new Cat('Sweet');myCat.constructor.name // => 'Cat'Object.prototype.toString.call(myCat) // => '[object Object]'
Because name
returns an empty string for an anonymous function (however in ES6 the name can be inferred), this approach should be used carefully.
Conclusion
The constructor
property is a piece of the inheritance mechanism in JavaScript. Precautions should be taken when creating hierarchies of classes.
However it offers nice alternatives to determine the type of an instance.
See also
Object.prototype.constructor
What's up with the constructor property in JavaScript?