DEV Community 👩‍💻👨‍💻

Cover image for Object Oriented JavaScript part 4
Tristan Elliott
Tristan Elliott

Posted on • Updated on

Object Oriented JavaScript part 4

Introduction

This is part 4 of my notes on The principles of object oriented programming in JavaScript by Nicholas C. Zakas. This post will be on constructors and prototypes. If you have questions or concerns about this post feel free to reach out to me on twitter.

Constructor

  • A constructor is a function that is used in combination with the new operator to create an object. The only difference between a constructor and a normal function, is the addition of the new operator and the name of the constructor beginning with a capital letter. A constructor allows us to creating consistent instances of objects. Since we can create our own constructors we can create our own reference types(objects)

               function person(){} // normal function
               function Person(){} // constructor function 
    
  • As you can see from above the only difference is the the first letter. When using a constructor function make sure it is called with the new operator.

               function Person(){}
               let person1 = new Person()
    
  • The new operator automatically creates an object of a given type and returns it. Also, the "this" value of the constructor function now references the object that was created by new instead of the default global scope.

             function Person(name){
                this.name = name
                this.sayName = function(){
                     console.log(this.name)
                }
             }
             let person1 = new Person("Bob")
             person1.sayName() // will output Bob
    
  • If you try to use the "this" value inside of a normal function you will get undefined. The "this" value inside a normal function is bound to the global scope. If the constructor is not called with the new operator then the "this" value will be bound to the global object. As you might of already noticed there is no return value inside of the constructor function. The new operator automatically produces a return value. If you do return a value and it is a reference type(object) then that object will be used for the "this" value. If you return a privative type, it will be ignored.

  • While constructors are very useful they alone do not eliminate code redundancy, which is a very important part of object oriented programming. If we create a person constructor and then call it 100 times, we still have 100 different instances of that code. The solution to this problem is the JavaScript prototype.

Prototype

  • You can think of prototypes in JavaScript as recipes for objects. Almost all functions (with the exception of some built in functions) have a prototype property that is used during the creation of new object instances. Those instances can access all the properties on that prototype

               let person ={
                  name:"Bob"
               }
               person.hasOwnProperty("name") // true
    
  • hasOwnProperty() is defined on Object.prototype so it can be accessed from any object as if the object had it as its own property.

The [[Prototype]] Property

  • An instance keeps track of its prototype through an internal property called [[prototype]]. When we create a new object using new, the [[prototype]] property of that object points back to the constructor function's prototype.

             const Person(){}
    
             const person1 = new Person()
             const person2 = new Person()
    
    • The internal [[prototype]] property for both person1 and person2 will point back to the prototype of the person constructor.
  • When a property is read on an object the JavaScript engine first checks the current instance of the object. If the property is not found then it searches the [[prototype]] object instead. If the property is still not found then undefined is returned.

Using Prototypes with Constructors

  • It is much more efficient to put methods on the prototype and then use "this" to access the current instance object.

            function Person(name){
                this.name = name
            }
           Person.prototype.sayName = function(){
                 console.log(this.name)
           }
    
  • sayName() is now defined on the prototype instead of the constructor meaning it is now shared by all Person instances via the prototype. This is how we can reduce code redundancy if we have many instances of a single constructor. However there is a warning when using reference types(objects) on the prototype. The reference type can be changed by any instance of the constructor. The code block below will do a better job of explaining it.

          function Person(){}
          Person.prototype.friends =[]
    
          const person1 = new Person()
          const person2 = new Person()
    
          person1.friends.push('Bob')
          person2.friends.push('Tim')
    
          console.log(person1.friends) // ['Bob','Tim']
    
  • As you can tell from the code above, person1 and person2 both shared the same friends array(this was not intended). Putting a reference type on the prototype is strongly cautioned against.

  • Now we could keep typing out Person.prototype. any time we wanted to add something to the Person prototype but there is an easier way. We assign an object literal to the prototype.

        function Person(name){
          this.name = name
        }
    
        Person.prototype = {
           sayName:function(){
              console.log(this.name)
           },
           speak:function(){
              console.log('it do be like that sometimes')
           }
        }
    
  • As you can see this pattern eliminates the need for us to keep typing Person.prototype. anytime we want to add something to the Person prototype. Instead we just create our own prototype and define everything we want at once. However, using the object literal syntax overwrites the constructor property that points to the constructor function. Instead of pointing to the Person constructor it now points to Object. To correct this error we just need to add our own constructor property.

          Person.prototype = {
           constructor:Person, // <-- added constructor 
           sayName:function(){
              console.log(this.name)
           },
           speak:function(){
              console.log('it do be like that sometimes')
           }
        }
    
  • If you are going to use this pattern then it is good practice to make sure that constructor is the first property.

Changing Prototypes

  • Because all instances of a particular type reference a shared prototype, you can augment all of those objects together. Any changes to the prototype are immediately available on any instance referencing it.

Built-in Object Prototypes

  • Wondering if you can modify built in prototypes? The answer is yes you can. If you modify the Array.prototype then all array instances will have that modification. However, it is recommended that we should stay away from modifying built in prototypes. This is because other developers expect the Built in object types to behave a certain. It is our job as developers to provide a consistent experience through our code.

Conclusion

  • This marks the end of part 4 on object oriented programming in JavaScript. Please make sure to be on the look ok for my next blog post which will be on Inheritance. If you have any questions or concerns about this post please let me know on twitter.

Top comments (0)

🌚 Browsing with dark mode makes you a better developer.

It's a scientific fact.