In this set of staged examples we show how we can use objects to structure our data and write better, more maintainable code.
- generate diffs with a script (jack)
- review diffs
- Delete this todo section
index.html
is a short web page that contains just a heading and an empty canvas. This page does not change throughout the stages.index.js
is a program that draws a crimson rectangle on the canvas. The properties of the rectangle are held in five variables.
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.
Stage 2: A drawRect
function (see the diff)
- We simplify the program by creating a
drawRect
function, so that the repetition when drawing rectangles is removed. We can now change the way all rectangles are drawn by changing this one function.
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
drawRect
function now takes just two parameters.
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
Class
which formalises the structure of our objects.
- In programming language terms, we can define a
- Here we create a
Rectangle
class.- By convention class names begin with a capital letter and are singular (e.g. Rectangle, not Rectangles).
- Classes have a
constructor
function 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
Rectangle
class) is now created using a single line of code.
Stage 5: Function encapsulation (see the diff)
- The
drawRect
function is specific to rectangles, so it can become part of theRectangle
object.- 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
draw
function, each instance of rectangle can be asked to draw itself.
Stage 6: A Circle (see the diff)
- We add a
Circle
class to complement ourRectangle
. - Like rectangles, circles have
x
&y
positions and acolour
, but nowidth
orheight
: instead they have a radiusr
. - The
draw
function is rewritten so that instances ofCircle
can draw themselves. - Now we can create and draw two circles.
Stage 7: Superclasses and subclasses (see the diff)
- In the previous stage, there is some duplication in the properties (
x
,y
andcol
). - We refactor the
Rectangle
andCircle
classes, taking their common properties and moving them to a new class calledShape
.Rectangle
andCircle
are now subclasses of theShape
superclass.- The
Shape
class contains properties that are common to all shapes. Rectangle
andCircle
only 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.
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
.mjs
extension is a convention used for JavaScript modules.
- Here, we can move the class definitions into
- JavaScript modules
export
functions and variables. Theimport
keyword is used to make these available in other files.- The
script
element inindex.html
must specify thattype=module
– only then areimport
andexport
allowed. - 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 start
will run a local web server on your machine.
- The
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
draw
multiple times. - The
classes.mjs
file 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.
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
moveBy
function that accepts two parameters (x
&y
) and adds these to the existingx
andy
properties of the instance. - We use this method to move all shapes to the right and down, then draw them a second time.
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
get
orset
before 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
x
property, whose value is internally stored in the_x
property.- Inside the setter, we check the given value is a number.
- In
index.js
, we set thex
property of every shape to 50 – the setter gets invoked to do this.
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
Shape
we define a getter forarea
that throws an error message, so ifShape
is extended but thearea
method is not implemented, accessingarea
will give meaningful feedback. - In
Rectangle
andCircle
we definearea
getter functions using appropriate formulae. - In
index.js
we can use thearea
getters as if they were properties on our shapes, logging the value to the console.
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.mjs
we use private properties like#x
and#y
to hide properties that should be treated as implementation details.