Book Image

Object-Oriented JavaScript - Third Edition

By : Ved Antani, Stoyan STEFANOV
5 (1)
Book Image

Object-Oriented JavaScript - Third Edition

5 (1)
By: Ved Antani, Stoyan STEFANOV

Overview of this book

JavaScript is an object-oriented programming language that is used for website development. Web pages developed today currently follow a paradigm that has three clearly distinguishable parts: content (HTML), presentation (CSS), and behavior (JavaScript). JavaScript is one important pillar in this paradigm, and is responsible for the running of the web pages. This book will take your JavaScript skills to a new level of sophistication and get you prepared for your journey through professional web development. Updated for ES6, this book covers everything you will need to unleash the power of object-oriented programming in JavaScript while building professional web applications. The book begins with the basics of object-oriented programming in JavaScript and then gradually progresses to cover functions, objects, and prototypes, and how these concepts can be used to make your programs cleaner, more maintainable, faster, and compatible with other programs/libraries. By the end of the book, you will have learned how to incorporate object-oriented programming in your web development workflow to build professional JavaScript applications.
Table of Contents (25 chapters)
Object-Oriented JavaScript - Third Edition
Credits
About the Authors
About the Reviewer
www.PacktPub.com
Customer Feedback
Preface
Built-in Functions
Regular Expressions

Chapter 6, Inheritance


Lets solve the following exercise:

Exercises

  1. Multiple inheritance by mixing into the prototype, for example:

            var my = objectMulti(obj, another_obj, a_third, { 
              additional: "properties" 
            }); 
            A possible solution: 
            function objectMulti() { 
              var Constr, i, prop, mixme; 
     
            // constructor that sets own properties 
            var Constr = function (props) { 
              for (var prop in props) { 
                this[prop] = props[prop]; 
              } 
            }; 
     
           // mix into the prototype 
           for (var i = 0; i < arguments.length - 1; i++) { 
             var mixme = arguments[i]; 
             for (var prop in mixme) { 
               Constr.prototype[prop] = mixme[prop]; 
             } 
           } 
     
          return new Constr(arguments[arguments.length - 1]);
       } 
    

    Testing:

            > var obj_a = {a: 1}; 
            > var obj_b = {a: 2, b: 2}; 
            > var obj_c = {c: 3}; 
            > var my = objectMulti(obj_a, obj_b, obj_c, {hello: "world"}); 
            > my.a; 
             2 
    

    Property a is 2 because obj_b overwrote the property with the same name from obj_a (last one wins):

            > my.b; 
            2 
            > my.c; 
            3 
            > my.hello; 
            "world" 
            > my.hasOwnProperty('a'); 
            false 
            > my.hasOwnProperty('hello'); 
            true 
    
  2. Practice with the canvas example at http://www.phpied.com/files/canvas/.

    Draw a few triangles using the following code snippet:

            new Triangle( 
              new Point(100, 155), 
              new Point(30, 50), 
              new Point(220, 00)).draw(); 
     
            new Triangle( 
              new Point(10, 15),   
              new Point(300, 50), 
              new Point(20, 400)).draw(); 
    

    Draw a few squares using the following code snippet:

            new Square(new Point(150, 150), 300).draw(); 
            new Square(new Point(222, 222), 222).draw(); 
    

    Draw a few rectangles using the following code snippet:

            new Rectangle(new Point(100, 10), 200, 400).draw(); 
            new Rectangle(new Point(400, 200), 200, 100).draw(); 
    
  3. To add Rhombus, Kite, Pentagon, Trapezoid, and Circle (reimplements draw()), use the following code:

            function Kite(center, diag_a, diag_b, height) { 
              this.points = [ 
                new Point(center.x - diag_a / 2, center.y), 
                new Point(center.x, center.y + (diag_b - height)), 
                new Point(center.x + diag_a / 2, center.y), 
                new Point(center.x, center.y - height) 
              ]; 
              this.getArea = function () { 
                return diag_a * diag_b / 2; 
              }; 
            } 
     
            function Rhombus(center, diag_a, diag_b) { 
              Kite.call(this, center, diag_a, diag_b, diag_b / 2); 
            } 
     
            function Trapezoid(p1, side_a, p2, side_b) { 
              this.points = [p1, p2, new Point(p2.x + side_b, p2.y),
              new Point(p1.x + side_a, p1.y) 
              ]; 
     
              this.getArea = function () { 
                var height = p2.y - p1.y; 
                return height * (side_a + side_b) / 2; 
              }; 
            } 
       
            // regular pentagon, all edges have the same length 
            function Pentagon(center, edge) { 
              var r = edge / (2 * Math.sin(Math.PI / 5)), 
                  x = center.x, 
                  y = center.y; 
     
              this.points = [new Point(x + r, y),
            new Point(x + r * Math.cos(2 * Math.PI / 5), y - r * 
             Math.sin(2 * Math.PI / 5)), 
            new Point(x - r * Math.cos(    Math.PI / 5), y - r * 
             Math.sin(    Math.PI / 5)), 
            new Point(x - r * Math.cos(    Math.PI / 5), y + r * 
             Math.sin(    Math.PI / 5)), 
            new Point(x + r * Math.cos(2 * Math.PI / 5), y + r * 
             Math.sin(2 * Math.PI / 5)) 
              ]; 
     
              this.getArea = function () { 
                return 1.72 * edge * edge; 
              }; 
            } 
     
            function Circle(center, radius) { 
              this.getArea = function () { 
                return Math.pow(radius, 2) * Math.PI; 
              }; 
       
              this.getPerimeter = function () { 
                return 2 * radius * Math.PI; 
              };   
       
              this.draw = function () { 
                var ctx = this.context; 
                ctx.beginPath(); 
                ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI); 
                ctx.stroke(); 
              }; 
            } 
     
            (function () { 
              var s = new Shape(); 
              Kite.prototype = s; 
              Rhombus.prototype = s; 
              Trapezoid.prototype = s; 
              Pentagon.prototype = s; 
              Circle.prototype = s; 
            }()); 
    

    Testing:

            new Kite(new Point(300, 300), 200, 300, 100).draw(); 
            new Rhombus(new Point(200, 200), 350, 200).draw(); 
            new Trapezoid( 
              new Point(100, 100), 100,  
              new Point(50, 250), 400).draw(); 
            new Pentagon(new Point(400, 400), 100).draw(); 
            new Circle(new Point(500, 300), 270).draw(); 
    

    The result of testing new shapes

  4. Think of another way to do the inheritance part. Use uber so kids can have access to their parents. Also, get parents to be aware of their children.

    Keep in mind that not all children inherit Shape; for example, Rhombus inherits Kite and Square inherits Rectangle. You end up with something like this:

            // inherit(Child, Parent) 
            inherit(Rectangle, Shape); 
            inherit(Square, Rectangle); 
    

    In the inheritance pattern from the chapter and the previous exercise, all children were sharing the same prototype, for example:

            var s = new Shape(); 
            Kite.prototype = s; 
            Rhombus.prototype = s; 
    

    While this is convenient, it also means no one can touch the prototype because it will affect everyone else's prototype. The drawback is that all custom methods need to own properties, for example this.getArea.

    It's a good idea to have methods shared among instances and defined in the prototype, instead of recreating them for every object. The following example moves the custom getArea() methods to the prototype.

    In the inheritance function, you'll see the children only inherit the parent's prototype. So own properties such as this.lines will not be set. Therefore, you need to have each child constructor call its uber in order to get the own properties, for example:

            Child.prototype.uber.call(this, args...) 
    

    Another nice-to-have feature is carrying over the prototype properties already added to the child. This allows the child to inherit first and then add more customizations or the other way around as well, which is just a little more convenient.

            function inherit(Child, Parent) { 
              // remember prototype 
              var extensions = Child.prototype; 
     
              // inheritance with an intermediate F() 
              var F = function () {}; 
               F.prototype = Parent.prototype; 
              Child.prototype = new F(); 
              // reset constructor 
              Child.prototype.constructor = Child; 
              // remember parent 
              Child.prototype.uber = Parent; 
     
              // keep track of who inherits the Parent 
              if (!Parent.children) { 
                Parent.children = []; 
              } 
              Parent.children.push(Child); 
     
              // carry over stuff previsouly added to the prototype 
              // because the prototype is now overwritten completely 
              for (var i in extensions) { 
                if (extensions.hasOwnProperty(i)) { 
                  Child.prototype[i] = extensions[i]; 
                } 
              } 
            } 
    

    Everything about Shape(), Line(), and Point() stays the same. The changes are in the children only:

            function Triangle(a, b, c) { 
              Triangle.prototype.uber.call(this); 
              this.points = [a, b, c]; 
            } 
     
            Triangle.prototype.getArea = function () { 
              var p = this.getPerimeter(), s = p / 2; 
              return Math.sqrt(s * (s - this.lines[0].length) * 
            (s - this.lines[1].length) * (s - this.lines[2].length)); 
            }; 
     
     
            function Rectangle(p, side_a, side_b) { 
              // calling parent Shape() 
              Rectangle.prototype.uber.call(this); 
     
              this.points = [ p, 
                new Point(p.x + side_a, p.y), 
                new Point(p.x + side_a, p.y + side_b), 
                new Point(p.x, p.y + side_b) 
              ]; 
            } 
     
           Rectangle.prototype.getArea = function () { 
               // Previsouly we had access to side_a and side_b  
               // inside the constructor closure. No more. 
              // option 1: add own properties this.side_a and this.side_b 
              // option 2: use what we already have: 
              var lines = this.getLines(); 
              return lines[0].length * lines[1].length; 
            }; 
     
     
            function Square(p, side) { 
              this.uber.call(this, p, side, side); 
              // this call is shorter than Square.prototype.uber.call() 
              // but may backfire in case you inherit  
              // from Square and call uber 
              // try it :-) 
            } 
    

    Inheritance:

            inherit(Triangle, Shape); 
            inherit(Rectangle, Shape); 
            inherit(Square, Rectangle); 
    

    Testing:

            > var sq = new Square(new Point(0, 0), 100); 
            > sq.draw(); 
            > sq.getArea(); 
            10000 
    

    Testing that instanceof is correct:

            > sq.constructor === Square; 
            true 
            > sq instanceof Square; 
            true 
            > sq instanceof Rectangle; 
            true 
            > sq instanceof Shape; 
            true 
    

    The children arrays:

            > Shape.children[1] === Rectangle; 
            true 
            > Rectangle.children[0] === Triangle; 
            false 
            > Rectangle.children[0] === Square; 
            true 
            > Square.children; 
            undefined 
    

    And uber looks ok too:

            > sq.uber === Rectangle; 
            true 
    

    Calling isPrototypeOf() also returns expected results:

            Shape.prototype.isPrototypeOf(sq); 
            true 
            Rectangle.prototype.isPrototypeOf(sq); 
            true 
            Triangle.prototype.isPrototypeOf(sq); 
            false 
    

    The full code is available at http://www.phpied.com/files/canvas/index2.html, together with the additional Kite(), Circle(), and so on from the previous exercise.