In this set of staged examples we show how we can use objects to structure our data and write better, more maintainable code.
index.htmlis a short web page that contains just a heading and an empty canvas. This page does not change throughout the stages.index.jsis a program that draws a crimson rectangle on the canvas. The properties of the rectangle are held in five variables.- See it.
Stage 1: A second rectangle (see the diff)
- In this stage we add a steel-blue rectangle.
- If we wanted to add more rectangles this would get repetitive and laboured.
- There is repetition in both the implicit structure of the variables for each rectangle, and the code to draw the rectangles.
- See it.
Stage 2: A drawRect function (see the diff)
- We simplify the program by creating a
drawRectfunction, so that the repetition when drawing rectangles is removed. We can now change the way all rectangles are drawn by changing this one function. - See it.
Stage 3: Using objects (see the diff)
- We group the rectangle properties in objects, to structure the code better.
- This allows us to simplify the property names.
- The
drawRectfunction now takes just two parameters. - See it.
Stage 4: Data encapsulation (see the diff)
- The two rectangles in previous stages are similar; informally we might describe them as instances of the same class of objects, which we can name rectangle.
- In programming language terms, we can define a
Classwhich formalises the structure of our objects.
- In programming language terms, we can define a
- Here we create a
Rectangleclass.- By convention class names begin with a capital letter and are singular (e.g. Rectangle, not Rectangles).
- Classes have a
constructorfunction that is called when a new instance of the class is created. - Classes provide a standardised way of ensuring that every object has the same basic properties, and constructors set their initial values.
- Each rectangle object (i.e. each instance of the
Rectangleclass) is now created using a single line of code. - See it.
Stage 5: Function encapsulation (see the diff)
- The
drawRectfunction is specific to rectangles, so it can become part of theRectangleobject.- In object-oriented terminology:
- Functions inside classes are known as methods.
- Methods are invoked (not called) on objects.
- When a method is invoked, the object on which it was called can be accessed through a special variable named
this.
- In object-oriented terminology:
- Once the Rectangle class includes a
drawfunction, each instance of rectangle can be asked to draw itself. - See it.
Stage 6: A Circle (see the diff)
- We add a
Circleclass to complement ourRectangle. - Like rectangles, circles have
x&ypositions and acolour, but nowidthorheight: instead they have a radiusr. - The
drawfunction is rewritten so that instances ofCirclecan draw themselves. - Now we can create and draw two circles.
- See it.
Stage 7: Superclasses and subclasses (see the diff)
- In the previous stage, there is some duplication in the properties (
x,yandcol). - We refactor the
RectangleandCircleclasses, taking their common properties and moving them to a new class calledShape.RectangleandCircleare now subclasses of theShapesuperclass.- The
Shapeclass contains properties that are common to all shapes. RectangleandCircleonly contain code specific to their particular shape.- The process of refactoring classes to create a new superclass is called generalisation.
- Notice that in the constructor of a subclass, its superclass constructor is called using the
super()function – this reduces duplication of code. - See it.
Stage 8: A file for classes (see the diff)
- When code gets longer, often it's a good idea to modularise by moving independent pieces into separate files:
- Here, we can move the class definitions into
classes.mjs. - The
.mjsextension is a convention used for JavaScript modules.
- Here, we can move the class definitions into
- JavaScript modules
exportfunctions and variables. Theimportkeyword is used to make these available in other files.- The
scriptelement inindex.htmlmust specify thattype=module– only then areimportandexportallowed. - For security reasons, module imports are not allowed to load local files, therefore we need to look at this example through a web server;
npm startwill run a local web server on your machine.
- The
- See it.
Stage 9: An array of shapes (see the diff)
- We are currently drawing four shapes. In the preceding stages, we stored them in four different variables. We treat them as a bag of shapes so it is better to use an array.
- The items in the array can be accessed in a loop, which reduces the need to call
drawmultiple times. - The
classes.mjsfile has not changed at this stage.- This shows the benefit of modularization: when changing the way we store the shapes we did not need to see how they are implemented.
- Not having the code in our editor means we cannot accidentally break it.
- See it.
Stage 10: Inheriting functions from superclasses (see the diff)
- A function that's defined in a superclass is inherited by all classes that extend the superclass.
- We define a
moveByfunction that accepts two parameters (x&y) and adds these to the existingxandyproperties of the instance. - We use this method to move all shapes to the right and down, then draw them a second time.
- See it.
Stage 11: Getters and Setters (and underscores) (see the diff)
- Getters and setters:
- are a special type of method, invoked when a property is read or written.
- are defined with the keyword
getorsetbefore the method name. - are used as if they were properties:
- when we read the value of the property, the getter is invoked, and we receive its return value.
- when we write into a property, the setter is invoked and receives, as its parameter, the value we wanted to set.
- are often used to access internal properties, so there is a convention of starting internal property names with an underscore character and the rest of the name the same.
- Here we add a getter & setter for the
xproperty, whose value is internally stored in the_xproperty.- Inside the setter, we check the given value is a number.
- In
index.js, we set thexproperty of every shape to 50 – the setter gets invoked to do this. - See it.
Stage 12: Getters for computed properties (see the diff)
- Here we add a getter for a new property:
area- This is not a normal property where we store a value, instead it's a computed property whose value depends on other properties.
- In
Shapewe define a getter forareathat throws an error message, so ifShapeis extended but theareamethod is not implemented, accessingareawill give meaningful feedback. - In
RectangleandCirclewe defineareagetter functions using appropriate formulae. - In
index.jswe can use theareagetters as if they were properties on our shapes, logging the value to the console. - See it.
Stage 13: Private fields (Private properties) (see the diff)
- The underscores seen previously are a common mechanism for programmers to informally communicate that a property is an implementation detail that is not intended for access or use by others.
- JavaScript has the (experimental) ability to define private fields in classes.
- Private fields are denoted by the hash symbol, used before the property name.
- Private fields must all be declared before the constructor.
- Private fields cannot be accessed from outside the object.
- They are only accessible from methods defined within the class.
- This means private fields cannot be accessed by subclasses, so getters/setters must be implemented if subclasses need read/write access.
- In this stage, in
classes.mjswe use private properties like#xand#yto hide properties that should be treated as implementation details. - See it.