Skip to main content
Engineering LibreTexts

5.4: Constructor Functions and Prototype Objects

  • Page ID
    27562
  • The purpose of this section is to continue to build the JavaScript Object Model. It will explain what a Constructor Function is, and how it can be used to create objects. It will show to define Constructor Functions to define how to build generic templates for creating objects, and how to use prototypes and prototype chains to add behavior easily to these object definitions.

    5.4.1 Constructor Function and Object Creation

    Property maps are a nice way to represent object properties and have many advantages over static class-based definitions of objects. But the way we have defined objects up to this point does not allow for default values, or for behaviors (functions) to be associated with the object without redefining the function for each object. JavaScript addresses these problems by allowing functions (called Constructor Functions) to build the object property maps. The JavaScript new operator is then used to set a variable to the property map created in the function.

    To understand how this works, consider the following program fragment that creates two Map objects, mapA and mapB. This is accomplished by setting both variables to different object definitions. The entire definition for this object, including the functions, must be copied.

    Program 91 - Creating two objects by setting them to different object literal values
    
    <script>
        let mapA = {
            title    : "MyMap",
            resize   : false,
            recenter : true,
            center   : [-77, 39],
            
            print: function() {
                console.log("title = " + this.title);
                console.log("resize = " + this.resize);
                console.log("recenter = " + this.recenter);
                console.log("center = " + this.center);
            }
        }
        
        let mapB = {
            title    : "MyMap1",
            resize   : false, 
            recenter : true,
            center   : [-100, 45],
            
            print: function() {
                console.log("title = " + this.title);
                console.log("resize = " + this.resize);
                console.log("recenter = " + this.recenter);
                console.log("center = " + this.center);     
            }
        }
        
        mapA.print();
        mapB.print();
    </script>                                     
    

    In the code in Program 91 above, the function print has access to a variable this, which is just a property map (or object) associate with the object. The use of the this variable will be used to define objects from a common function.

    The need to create two object definitions for the same object is inefficient and error prone. In JavaScript the ability to create a template for an object and use it to create objects is accomplished using a Constructor Function. A Constructor function is a function that constructs objects. It uses the this property map when the function is invoked, and builds the object that is to be used on the this object. The JavaScript new operator then makes the this property map accessible to assign to a variable.

    The following program shows the use of a Constructor Function Map and the new operator to create the equivalent mapA and mapB variables from Program 86 above. The objects mapA and mapB are exactly equivalent to the ones from Program 82, including the fact that they each contain their own copy of the print function.

    Program 92 - Using a Map Constructor Function
    
    <script>
        function Map() {
            this.title = "MyMap",
            this.resize = false,
            this.recenter = true,
            this.center = [-77, 39],
            
            this.print = function() {
                console.log("title = " + this.title);
                console.log("resize = " + this.resize);
                console.log("recenter = " + this.recenter);
                console.log("center = " + this.center);
            }
        }
        
        mapA = new Map();
        mapB = new Map();
        mapB.title = "MyMap1";
        mapB.center=[-100, 45];
        
        mapA.print();
        mapB.print();
    </script>                        
    

    Note that all of the properties for both maps, including the functions, are stored separately. There are two property maps created, one for each variable reference. These property maps include two separate print functions, as each property map will have a variable referencing a different print function. How to make a single print function will be covered later in this chapter.

    At this point, there are probably readers who want to equate a JavaScript Constructor Function with a Java class, and to equate the Java and JavaScript new operators. This is a completely wrong understanding of the new operator in the two languages. In Java a class is a template which includes functions and data, and the new operator creates an instance of that template. The Java new operator then calls a Java Constructor to initializes the variables in the constructed instance.

    To re-emphasize, a JavaScript Constructor Function is not a template for a class that is instantiated. A Constructor Function is a function that is invoked, and when executed assigns properties, including functions, to a property map. The property map is then made available to a variable using the new operator. The new operator plays no part in constructing the object or how the function properties are called. It is as wrong to equate how a Java and JavaScript object are created as it is to equate how Java and JavaScript define and use objects. Going down that path will in the end only lead to confusion.

    5.4.2 Passing parameters to a Constructor Function

    The Constructor Function provided in the previous example is not really useful because only the default values for properties can be set when the function is called. To be useful the function needs to have parameters which can be used to set the properties. The simple answer to this problem would be to have a parameter to each value to be set, as in the following example.

    Program 93 - Passing parameters to a Constructor Function
    
    <script>
        function Map(title, resize, recenter, center) {
            if (title == null)
                this.title = "MyMap";
            else
                this.title = title;
        
            if (resize == null)
                this.resize = false;
            else
                this.resize = resize;
            
            if (recenter == null)
                this.recenter = true;
            else
                this.recenter = recenter;
                
                if (center == null)
                this.center = [-77, 39];
            else
                this.center = center;
            
            this.print = function() {
                console.log("title = " + this.title);
                console.log("resize = " + this.resize);
                console.log("recenter = " + this.recenter);
                console.log("center = " + this.center);
            }
        }
        
        mapA = new Map("Map1", null, null, [55, 20]);
        mapB = new Map(null, true, null, null);
        
        mapA.print();
        mapB.print();
    </script>                                                                                
    

    This solution of adding a parameter for each default value is not a good solution. What happens when a value for a parameter is not defined, implying that the default value should be used? The value is passed as null in the program above, but what if null is a valid value to set to the variable? JavaScript also allows objects to contain values that would not have a default. How are these set?

    The solution used here is to pass an object (property map) to the JavaScript Constructor Function, with only the non-default properties in that parameter object. The following illustrates how to implement this solution with the Map Constructor. Note that not only is this code much shorter, to properly handles default values, and allows the object to have values that are not predefined with default values.

    Program 94 - Using a Constructor Function to reconstruct an object 
    
        function Map(options) {
        
        // Set default values
        this.title    = "MyMap";
        this.resize   = false;
        this.recenter = true;
        this.center   = [-77, 39];
            
            // Load values from options
        for (let prop in options) {
            // The property has no default value 
            if (!this.hasOwnProperty(prop)) {
                console.log("Property " + prop +
                " not recognized in Map");
            }
                this[prop] = options[prop];
        }
        
        this.print = function() {
            console.log("title = " + this.title);
            console.log("resize = " + this.resize);
            console.log("recenter = " + this.recenter);
            console.log("center = " + this.center);            
        }
    }
    
    mapA = new Map({title:"Map1", center: [55,20]});
    mapB = new Map({resize:true});
    
    mapA.print();
    mapB.print();
    </script>                    
    

    This example is the first where the true value and power of JavaScript objects and its object model as shown. But it is just the start of the amazing power of objects in JavaScript.

    5.4.3 Constructor Functions and JSON

    One nice feature of constructor functions is how nicely they work with JSON. Remember that when creating a JSON object, the functions are not serialized. Only the primitive data items are set in the JSON object. A map object serialized to JSON and then read back into the program will be missing the print function. The issue is how the print function can be added back to the object.

    To add the print function back to the object, the JSON object can be passed to the Constructor Function. The object that will be returned from the constructor function add back the methods which were originally part of the object. This is shown below in the Program 95. In this example, a Map object, mapA, is created and serialized into a JSON object. This JSON object is then set to objA, which has all the fields of the mapA object, but is not a Map object and it does not have a print function defined. The objA object is then passed to the Map Constructor function, where the properties of objA are copied into a new mapB object. The print method can then be called on the mapB object.

    Program 95 - Using a Constructor Function to reconstruct a JSON object
    
    <script>
        function Map(options) {
        
        // Set default values
        this.title    = "MyMap";
        this.resize   = false;
        this.recenter = true;
        this.center   = [-77, 39];
        
        // Load values from options
        for (let prop in options) {
            // The property has no default value
            if (!this.hasOwnProperty(prop)) {
                console.log("Property " + prop +
                " not recognized in Map");
            }    
                this[prop] = options[prop];
        }
        
        this.print = function() {
        console.log("title = " + this.title);
        console.log("resize = " + this.resize);
        console.log("recenter = " + this.recenter);
        console.log("center = " + this.center);
        }
    }
    
    mapA = new Map({title:"Map1", center: [55,20]});
    mapA.print(); // works
    
    objA = JSON.parse(JSON.stringify(mapA));
    // objA.print(); // This line fails,here is no print method for ObjA
    
    objB = new Map(objA)
    objB.print(); // print works, as objB is a Map
    </script>                                        
    

    This ability to use JSON to store data, and to later pass those JSON objects to the Constructor Functions and reconstruct the original object, will be extensively used later.

    5.4.4 Abstracting behavior and prototypes

    This chapter so far has emphasized the fact that the functions that are part of an object are copied into the property map of every object. While storing these functions as data with each object allows for exceptions in which specific objects can have behavior that is different for special instances of the object, it is obviously inefficient. What is needed is a way to store the functions once and have them be accessed by all the objects constructed by a single Constructor Function.

    This functionality should be implemented in a way consistent with the way the JavaScript language works, and this means using property maps (not class-based templates). A simple solution is to have Constructor Functions have a special property map associated with the function, and then have a link to the Constructor Function’s property map stored with each object to share properties across multiple objects. Then when a property, such as a function, is requested for an object, the object’s property map is searched for this property. If the property or function is not found in the object’s property map, the shared property map for the Constructor Function can then searched. Multiple shared property Maps can exist, and they can each be recursively searched in turn until either the property is found, or the end of the shared property maps is reached. This is shown in the Figure 16.

    Figure 16 - Recursive search for property in linked property maps

    Screen Shot 2020-07-04 at 3.41.43 PM.png

    This recursive shared property map concept is implemented in JavaScript and is called prototypes.

    Every function, not just Constructor functions, has associated with it a prototype object (property map).36 To see how this prototype object is used, the construction of the JavaScript object with the new operator needs to be understood.

    When the JavaScript new operator is invoked, it does two things.

    1. First it sets the variable on the left-hand-side (lhs) of the equal sign to the this variable that was used in the Constructor Function.
    2. Second a prototype variable (a link) in the object’s property map is set to point to the prototype object property map corresponding to the Constructor Function. The value of this link cannot be directly changed by the object but is accessed when a property is requested for the object that cannot be found in the objects property map.

    The behavior of recursively searching property maps, described earlier, can now be implemented. When a function is requested, the property map for the individual object is searched. If the function is not found, the search continues in the prototype map of the Constructor Function. If it is still not found, the search continues until either the function is found, or the root of the tree, the Object prototype, is reached.

    In the following example, the print method is moved from the individual objects, and stored in the Map protocol object. Now the two objects, MapA and MapB, both share the same print function.

    Program 96 - Accessing the print function in the Map prototype object
    
    <script>
        function Map(options) {
        
            // Set default values
            this.title    = "MyMap";
            this.resize   = false;
            this.recenter = true;
            this.center   = [-77, 39];
            
            // Load values from options
            for (let prop in options) {
                // The property has no default value
                if (!this.hasOwnProperty(prop)) {
                    console.log("Property " + prop +
                    " not recognized in Map");
                }
                    this[prop] = options[prop];
            }
        }
        
        Map.prototype.print = function() {
            console.log("title = " + this.title);
            console.log("resize = " + this.resize);
            console.log("recenter = " + this.recenter);
            console.log("center = " + this.center);
        }
        mapA = new Map({title:"Map1", center: [55,20]});
        mapB = new Map({title:"Map2"})
        
        // These two objects share the print method from the
        // Map prototype object.
        mapA.print();
        mapB.print();
    </script>                                                
    

    5.4.5 Inheritance and Polymorphism

    As I wrote the title to this section, I could just see the eyes rolling of a number of readers. Inheritance and polymorphism are always a problem in a class in Java/C#/C++/etc. Let me put everyone’s mind at ease. In JavaScript, if you understood prototypes from the last section, you already know these concepts in JavaScript.

    To see how polymorphism can be used in JavaScript, consider the following problem. You want the object mapB from Program 96 to have a different print method than the standard one that is defined for the Map Constructor Function. This is a trivial change, as all that is needed is to add a different print function to the mapB property map, and that function will be encountered first when looking for the print function for the mapB object. This is shown in the example Program 97.

    Program 97 – Polymorphism in JavaScript
    
    <script>
        function Map(options) {
        
        // Set default values
        this.title    = "MyMap";
        this.resize   = false;
        this.recenter = true;
        this.center   = [-77, 39];
        
                // Load values from options
                for (let prop in options) {
                // The property has no default value
                if (!this.hasOwnProperty(prop)) {
                    console.log("Property " + prop +
                    " not recognized in Map");
                }
                    this[prop] = options[prop];
            }
        }
        
        Map.prototype.print = function() {
            console.log("title = " + this.title);
            console.log("resize = " + this.resize);
            console.log("recenter = " + this.recenter);
            console.log("center = " + this.center);
        }
        
        mapA = new Map({title:"Map1", center: [55,20]});
        mapB = new Map({title:"Map2"});
        mapB.print = function() {
            console.log("Same print function, but polymorphic")
            console.log("title = " + this.title);
            console.log("resize = " + this.resize);
            console.log("recenter = " + this.recenter);
            console.log("center = " + this.center);
        }
        
        // These two objects share the print method from the
        // Map prototype object.
        mapA.print();
        mapB.print();
    </script>                                                        
    

    This changes the meaning of inherit from as used in Java and other class-based OOP languages. When discussing prototypes and JavaScript, the term inherit from means that the property map for the current object links to (inherits from) the prototype object’s property map, and the property maps are searched recursively for a property. This is, in a sense, how polymorphism is implemented in class-based languages, but it is so abstracted away that any sense of what is going on is lost to most programmers.

    To summarize, polymorphism, or calling different methods which have the same name, in JavaScript means that the property maps are searched until the first occurrence of that property is found.

    What is missing in this section is how to implement inheritance and composition delegation in JavaScript. There is a good reason for that. First, while the concept of inheritance is simple in JavaScript, changing the property map links is non-trivial; and until someone shows me a valid design using inheritance that cannot be implemented more easily and correctly using some other design mechanism, my advice is to not use inheritance. Thus, I never cover inheritance (other than interface inheritance, which is not the same as class inheritance) in any language.

    As for compositional delegation, this is normally solved in JavaScript using Functional Programming. While I might be more inclined to describe how to implement delegation, as it is not that difficult, until a see a real-life case where it is an advantage over Functional Programming in JavaScript, I see no need to cover something that is at best useful in one-off situations.

    5.2.1 JSON and prototype properties

    What happens to the protocol chain when an object is serialized to JSON? When serializing an object, only the properties that can be externally represented are written to the JSON object. For the prototype variable (link to a Constructor Function prototype object) there is no way to know if the corresponding Constructor Function will exist when this JSON object is loaded into the new environment, so it must be dropped from the JSON definition.

    But dropping the prototype link variable from the JSON object does not represent a problem. If the Constructor Function for the object is known, the appropriate Constructor Function can be called passing in the JSON object to reconstruct the original object, just as was done in Program 98. This is shown in the following program to reconstruct the prototype chain for Map objects37.

    Program 98 - Reconstructing the protocol chain for a JavaScript object
    
    <script>
        function Map(options) {
    
            // Set default values
            this.title    = "MyMap";
            this.resize   = false;
            this.recenter = true;
            this.center   = [-77, 39];
            
            // Load values from options
            for (let prop in options) {
                // The property has no default value
                if (!this.hasOwnProperty(prop)) {
                    console.log("Property " + prop + " not recognized in Map");
                }
                this[prop] = options[prop];
            }
        }
        
        Map.prototype.print = function() {
            console.log("title = " + this.title);
            console.log("resize = " + this.resize);
            console.log("recenter = " + this.recenter);
            console.log("center = " + this.center);
        }
        
        mapA = new Map({title:"Map1", center: [55,20]});
        
        jmapA = JSON.stringify(mapA);
        mapA1 = new Map(JSON.parse(jmapA));
        mapA1.print();
    </script>                                
    

    5.2.2 Finding the JavaScript Constructor Function in the DOM

    Calling the correct Constructor Function to reconstruct the object works fine so long as the correct Constructor Function is known for the JSON object. But what if a number of different functions, created with different Constructor Functions, are stored externally, and the program does not know what Constructor Function corresponds to each JSON object?

    Fortunately, there is an answer for this in JavaScript. All Constructor Functions are stored as lambda values in the DOM using the window property map. To retrieve and execute the Map Constructor Function using the object JSONObject can be done in the following line of code:

    var myMap = new window[“Map”](JSONObject);

    All that is needed to recreate the JSON object is the name of the Constructor Function, and that can be stored as a property in the JSON object itself. A strange looking name should be used so that it will not overlap with names the programmers might choose, so we will use the name “__cfName”. The new definition of the Map Constructor Function will now be the following:

    Program 99 - Map Constructor Function setting the __cfName variable
    
    <script>
        function Map(options) {
        
            // Set default values
            this.__cfName = "Map";
            this.title    = "MyMap";
            this.resize   = false;
            this.recenter = true;
            this.center   = [-77, 39];
            
                // Load values from options
                for (let prop in options) {
                    // The property has no default value
                    if (!this.hasOwnProperty(prop)) {
                        console.log("Property " + prop +
                        " not recognized in Map");
                    }
                        this[prop] = options[prop];
                }
        }
        Map.prototype.print = function() {
            console.log("title = " + this.title);
            console.log("resize = " + this.resize);
            console.log("recenter = " + this.recenter);
            console.log("center = " + this.center);
        }
    
    </script>                                                   
    

    Now that the name of the Constructor Function to call is known, the following function, getObjectFromJ can reconstruct any JSON object using the correct Constructor Function.

    Program 100 - getObjectFromJSON function
    
    // The input parameter is the JSON object
    function getObjectFromJSON(string) {    
        let parsedObject = JSON.parse(string);    
        let cf = parsedObject["__cfName"];
        return new window[cf](parsedObject);
    }    
    

    The following example shows how to use the getObject function on a JSON object that represents a Map.

    Program 101 - Using the getObjectFromJSON function to reconstruct the object
    
    <script>
        function Map(options) {
            
            // Set default values
            this.__cfName = "Map";
            this.title    = "MyMap";
            this.resize   = false;
            this.recenter = true;
            this.center   = [-77, 39];
                
                // Load values from options
           for (let prop in options) {
                    // The property has no default value
                if (!this.hasOwnProperty(prop)) {
                    console.log("Property " + prop +
                    " not recognized in Map");
                }
                    this[prop] = options[prop];
            }
        }
        
        Map.prototype.print = function() {
            console.log("title = " + this.title);
            console.log("resize = " + this.resize);
            console.log("recenter = " + this.recenter);
            console.log("center = " + this.center);
        }
        
        // The input parameter is the JSON object
        function getObjectFromJSON(string) {    
            let parsedObject = JSON.parse(string);
            let cf = parsedObject["__cfName"];
            return new window[cf](parsedObject);
        }
        
        mapA = new Map({title:"Map1", center: [55,20]});
        
        // Create the JSON object, and then pass it to getObjectFromJSON
        // to show that the function does indeed reconstruct the object.
        jmapA = JSON.stringify(mapA);
        mapA1 = getObjectFromJSON(jmapA);
        mapA1.print();
    </script>                                                                            
    

    36 Unless a function is a Constructor Function, this prototype object is not used, but the function will still have prototype objects associated with them.

    37 The protocol chain for a JSON object can be updated more simply by using the JavaScript setPrototypeOf function. Use of this function is, however, strongly discouraged. The setPrototypeOf function must change many structures in the program to optimize how prototype chains are optimized, and is a very expensive function to call. Also, any default code that is normally executed when constructing an object is not run. For these reasons, it is recommended that a new variable with the proper prototype constructor be created. This text will recommend that to create a new object, a correct Constructor Function be written that sets all default property values and saves all property values of the original object be created, and that Constructor Function be called using the JSON object.

    • Was this article helpful?