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.
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:
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:
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
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); }