25 Classes and Objects
Up until now you have have used objects extensivley (e.g. the document object) and you have created your own objects using object literals, but you have not been programming in an object-oriented manner. In this chapter, you will learn some of the ways in which JavaScript also supports object-oriented programming (OOP), allowing you to add methods and create object types (classes) that can be reused.
Connections
Here’s a roundup of what you should know so far about objects.
Associative Arrays: A JavaScript object is an associative array (sometimes known as a dictionary or a map). An associative array links keys to values. Each key is an instance variable of that object.
Notation: You can access an object using the dot operator or by using associative array brackets. For example node.style.color is the same as node.style[“color”] and node[“style”][“color”].
Flexibility: In JavaScript, the structure of an object can be changed after it is created. If you try node.innerHtml = “hello”, where node is a DOM element object, you will not see any change on the page. That’s because the correct field name was innerHTML, but you created a new field called innerHtml (the case is different).
Object Literals: You can use braces to create an object literal and assign that literal to a variable. For example {x: 12, y: -15} creates an object with two fields (instance variables) named x and y, set to 12 and -15 respectively.
The Object-Oriented Mindset
The central idea behind object-oriented programming (OOP) is that a programmer should view a program as a set of objects with states and behaviors. This is in contrast to a procedural programmer who might begin by breaking down a program into tasks and thinking about the order in which to perform those tasks.
For example, in designing a brick-breaker game, a procedural programmer might first think about the central algorithms such as the game update algorithm, collision detection algorithms, drawing algorithms, and so on. An object-oriented programmer, on the other hand, would first consider what kinds of objects are in the game. They would probably identify balls, paddles, bricks, maybe the scoreboard, and so on. Then they would design and test each object type separately before putting it all together.
Objects with Methods
In object-oriented programming, an object has a state and behaviors. The state is defined by its instance variables (fields), while its behaviors are defined by its methods. A method is a function that is bound to a particular object and can only be accessed through that object.
Here’s an example, based on the ball object from the last chapter:
let ball = { x = 350, y = 200, xSpeed = -2, ySpeed = -0.5, moveOneStep = function() { this.x += this.xSpeed; this.y += this.ySpeed; } }
In the above example, the ball object tracks its location and speed in its four fields (x, y, xSpeed, ySpeed). It’s fifth instance variable is moveOneStep, but this variable has been assigned a function value. This makes it a method. When it is called, it modifies the ball object’s x and y variables. Note the use of the this keyword, required to access the fields of “this” object (i.e., the ball).
The moveOneStep method can be called like this:
ball.moveOneStep()
This can lead to a very nice separation of concerns, or modularity in the code for complicated apps. We can now encapsulate any logic that has to do with the ball inside the methods of this one object, and call the methods there whenever we need to change the state or find out something the object. This can make the rest of our code more readable and more maintainable while also making it easy for multiple programmers to work on the same project (each programmer designs and tests one object or a set of objects).
Do it Yourself
Get sampleCanvasAnimationWithMethods.html from the Full Stack Example Pack and compare it to sampleCanvasAnimation.html from the last chapter. Identify the two methods of the ball object, and then take a look at how this simplifies the updateAnimation function.
Add a grow method to increase the radius of the ball. The amount to increase should be passed as a parameter to the grow method. Then add a call to that method into the updateAnimation function and run it to make sure the ball increases in size (best to use a small increase like 0.05).
Objects and Classes
Now you can create objects with both fields and methods using an object literal. But if you wanted to add a second and third object of the same type (for example, multiple balls in an animation), you would end up duplicating a lot of code. This is where classes come in. Most object-oriented languages allow you to create a class to serve as a blueprint for an object. You write the code only once for a Ball class, then use it to create as many Ball objects as you want.
Here’s a class definition that could be used to create ball objects:
class Ball { x = 350; y = 200; xSpeed = -1; ySpeed = -0.5; moveOneStep = function() { this.x += this.xSpeed; this.y += this.ySpeed; } }
The Ball class looks a bit like an object literal with a slightly different syntax (semicolons instead of commas, the class keyword, etc.). But it’s not an object.
Now that we have a Ball class, we can create any number of Ball objects using the new keyword, and the () operator:
let b1 = new Ball(); let b2 = new Ball();
Each Ball object contains its own set of x, y, xSpeed, and ySpeed variables. When we call moveOneStep on a Ball object, it accesses the variables that belong to that object only.
b1.moveOneStep(); // update b1's x and y b2.xSpeed = -10; // set b2's xSpeed b2.moveOneStep(); // update b2's x and y
Connections
Notice that the class Ball starts with a capital letter? In virtually all programming languages that contain classes (Java, C++, C#, Python, JavaScript, PHP, etc.) it is a documentation convention that class names start with a capital letter while variable, method, and function names start with lowercase letters. This convention is almost universally adhered to by software developers because it greatly aids readability.
Constructors
One issue with the Ball class in the previous section is that all the Ball objects are created with the same values for x, y, xSpeed, and ySpeed. So if you wanted to create Ball objects in different locations with different speeds, you would have to set all their variables after you created them.
To make object creation easier, JavaScript provides a special constructor method. Here’s a new version of the Ball class with a constructor added that sets values for all four variables:
class Ball { constructor(x, y, xSpeed, ySpeed) { this.x = x; this.y = y; this.xSpeed = -xSpeed; this.ySpeed = -ySpeed; } moveOneStep = function() { this.x += this.xSpeed; this.y += this.ySpeed; } }
If a constructor is present, it is automatically called when a Ball object is created. Since it takes 4 parameters, you now have to create Ball objects with 4 arguments:
b1 = new Ball(0, 0, -1, 0.5); b2 = new Ball(350, 200, 0, 0);
With the constructor added, you can now create Ball objects and set their variables all on one line.
Do it Yourself
Get sampleCanvasAnimationWithClasses.html from the Full Stack Example Pack and take a look at the code. You will see a class definition for the Ball class followed by the load event listener.
class Ball { // class definition here } window.addEventListener("load", function(event) { // main code here }
This structure creates a nice separation of concerns between Ball code and main program code. You could make the separation even cleaner by putting the class definition and the main program code into two separate files and loading them in two separate script elements.
If you load the file into a browser, you’ll see two balls with different colors and speeds. Add a third ball with yet another color and speed.
Notice that the animation still stops when b1 hits a wall. Create a new Ball method to detect wall collisions on a single object (returning true if there is a collision, and false if not) and the call it for each Ball and stop the animation when the first of the balls hits the wall.
Connections
Since Simula in the 1960’s, most object oriented programming languages have used classes and constructors as shown here. But in the 1980’s and 1990’s, languages like Self pioneered a classless approach to object-oriented programming. Self had prototype objects that could used to create other objects of the same type, but it had no classes.
JavaScript got caught up in the latter trend and took the path of object prototypes instead of classes. For the first twenty years of its existence, JavaScript did not contain classes. They were added in 2015 for version 6 of the language. As a result there are lots of other ways to do object-oriented programming in JavaScript that don’t involve classes.
<exercises chapter=”classes”>
- Create a class for a GuessingGame object. This game object should choose a random integer in its constructor (use Math.floor(Math.random()*___+1) ). The object should have a guess method that accepts a number as a parameter and returns a code to indicate whether the number is correct, too high, or too low. Now create a JavaScript app with a form to allow the user to make guesses. When the app starts, create a new GuessingGame object. When the user submits a guess, call the guess method and put a message on the screen in response. If they guess correctly, replace the GuessingGame object with a new one. If they make 10 incorrect guesses, it’s game over. Show an appropriate message and create a new GuessingGame object so that they can try again.
- Get sampleCanvasAnimationWithClasses.html from the Full Stack Example Pack and add a bounce method to the Ball class. This method should accept parameters for wall locations and then if it detects that the current x or y locations are beyond the walls, it should “bounce” by resetting xSpeed or ySpeed. For example, if the x value is smaller than the left wall, xSpeed should be forced to positive (if it’s < 0, multiply by -1). Then change the updateAnimation function so that the balls bounce forever.
- Get sampleCanvasAnimationWithClasses.html from the Full Stack Example Pack. Add a method to the Ball class called gravity. Its job is to add a constant amount to the ySpeed variable each time it’s called. The amount to add should be passed as a parameter. Then add a call to the gravity method in the updateAnimation function and experiment with different gravity values.
- Get sampleCanvasAnimationWithClasses.html from the Full Stack Example Pack. Add a method to the Ball class called fadeOneStep. This method should reduce the red, green, and blue values of a ball by a small amount so that they “fade” towards black. The amount should be given as a parameter. For example, if the amount is set to 5, the red, green, and blue values should be reduced by 5%. Then use a call to fadeOneStep in the main program to have the balls fade out as they move.
- Get sampleCanvasAnimationWithClasses.html from the Full Stack Example Pack and refactor the code so that instead of 1 or 2 Ball objects, there are 500 balls in an array. Use a loop to create them and use Math.random to generate initial positions, speeds, and colors. Then use a loop to advance them all by one step each time the updateAnimation function is called.
- The Pong Project. If you were working on Pong in the earlier chapters, it’s time to make your code properly object-oriented. Create classes for all the object types with constructors and methods, then refactor your code to make use of these classes.