Classes as object factories

(still a work in progress!)

We may have a need to create many objects that all have the same basic shape. As you might expect, a good way to accomplish this is to make a function that returns objects. In the following example, the randomCircle function creates an object with a handful of random attributes, which are then used to draw circles to the screen.

► run sketch ◼ stop sketch
function randomCircle() {
    let circ = {
        x: random(width),
        y: random(height),
        diam: random(300)
    };
    return circ;
}

let circles = [];

function setup() {
    createCanvas(400, 400);
}

function draw() {
    background(50);
    noFill();
    stroke(255);
    for (let i = 0; i < circles.length; i++) {
        ellipse(circles[i].x,
            circles[i].y,
            circles[i].diam,
            circles[i].diam);
        circles[i].y++;
    }
    if (frameCount % 30 == 0) {
        let circ = randomCircle();
        circles.push(circ);
    }
}

This sketch refines the idea a bit. Instead of a random circle, I defined a function that takes a number of parameters to initialize a circle object with particular properties:

► run sketch ◼ stop sketch
function createCircleObj(x, y, diam) {
    let circ = {
        x: x,
        y: y,
        diam: diam
    };
    return circ;
}

let circles = [];

function setup() {
    createCanvas(400, 400);
    // initialize with ten circles
    for (let i = 0; i < 10; i++) {
        let newCirc = createCircleObj(
            random(width),
            random(height),
            300);
        circles.push(newCirc);
    }
}

function draw() {
    background(50);
    stroke(255);
    noFill();
    // display circles
    for (let i = 0; i < circles.length; i++) {
        ellipse(circles[i].x, circles[i].y,
            circles[i].diam, circles[i].diam);
        if (circles[i].diam > 0) {
            circles[i].diam -= 1;
        }
    }
}

// add a new circle when mouse is pressed
function mousePressed() {
    let newCirc = createCircleObj(mouseX, mouseY,
        300);
    circles.push(newCirc);
}

That createCircleObj function is interesting to think about. We’ve essentially created a function that defines a new “type” of variable. “Type” here in the sense that our program may have a number of such variables, and they all share certain attributes (such as the x, y, and diam properties), so it’s reasonable to say they’re all the same kind of thing.

JavaScript provides an alternate syntax for specifying “types” of variables called classes. The above example could instead be written like this:

► run sketch ◼ stop sketch
class Circle {
    constructor(x, y, diam) {
        this.x = x;
        this.y = y;
        this.diam = diam;
    }
}

let circles = [];

function setup() {
    createCanvas(400, 400);
    // initialize with ten circles
    for (let i = 0; i < 10; i++) {
        let newCirc = new Circle(
            random(width),
            random(height),
            300);
        circles.push(newCirc);
    }
}

function draw() {
    background(50);
    stroke(255);
    noFill();
    // display circles
    for (let i = 0; i < circles.length; i++) {
        ellipse(circles[i].x, circles[i].y,
            circles[i].diam, circles[i].diam);
        if (circles[i].diam > 0) {
            circles[i].diam -= 1;
        }
    }
}

// add a new circle when mouse is pressed
function mousePressed() {
    let newCirc = new Circle(mouseX, mouseY,
        300);
    circles.push(newCirc);
}

In this new example, the createCircleObj function has been removed. There’s now a class definition. (It’s called a “class” definition because we’re defining a new “class” of object—a type of object that’s different from other types, in the same way that we might talk about the mammal “class” of animals.) Using the class syntax, you don’t call a function to create a new object, but instead “call” the class definition as though it were a function, with the word new in front of it:

let newCirc = new Circle(mouseX, mouseY, 300);

This is called instantiating an object from the class. (We’re going from the abstract idea of a type of object to an actual concrete example of the type. Like my cat Shumai is an object of the “Cat” class. Except I would never describe my cat as an “object,” she is a living, thinking citizen of this world whom I love dearly. But you get the idea.)

The class definition consists of the word class and the name of the class, followed by a block ({ and }) that has a weird looking sorta function-like thing labelled with constructor. Behind the scenes, JavaScript actually “calls” this constructor function whenever you instantiate an object of the class with the new keyword, passing the parameters that you specified in between the parentheses in the instantiation call.

Breaking it down

class Cat {
    constructor(name) {
        this.name = name;
        this.weight = 5 + Math.random() * 20;
    }
}
let c = new Cat("Shumai");
console.log(c); // Object { name: "Shumai", weight: 10.287323303216755 }

With methods

class Cat {
    constructor(name) {
        this.name = name;
        this.weight = 5 + Math.random() * 20;
    }
    meow() {
        console.log("Meow! My name is", this.name, "and I weigh", this.weight, "lbs.");
    }
}
let c = new Cat("Shumai");
c.meow(); // Meow! My name is Shumai and I weigh 23.946031392192616 lbs.

More circles

► run sketch ◼ stop sketch
class Circle {
    constructor(x, y, diam) {
        this.x = x;
        this.y = y;
        this.diam = diam;
    }
    display() {
        ellipse(this.x, this.y, this.diam, this.diam);
    }
    update() {
        if (this.diam > 0) {
            this.diam -= 1;
        }
    }
}

let circles = [];

function setup() {
    createCanvas(400, 400);
    // initialize with ten circles
    for (let i = 0; i < 10; i++) {
        let newCirc = new Circle(
            random(width),
            random(height),
            300);
        circles.push(newCirc);
    }
}

function draw() {
    background(50);
    stroke(255);
    noFill();
    // display circles
    for (let i = 0; i < circles.length; i++) {
        // much cleaner!
        circles[i].display();
        circles[i].update();
    }
}

// add a new circle when mouse is pressed
function mousePressed() {
    let newCirc = new Circle(mouseX, mouseY,
        300);
    circles.push(newCirc);
}

Further reading