A way of organizing objects
const p = { x: 10, y: 20 };
const point = (x, y) => {
return { x: x, y: y };
}
const p = point(10, 20);
const distance = (p1, p2) => {
return Math.hypot(p1.x - p2.x, p1.y - p2.y);
}
const origin = point(0, 0);
const p = point(10, 20);
console.log(distance(p, origin));
Behavior attached to objects are called “methods”.
Methods are like functions but are defined within the class.
Within methods the magic variable this
references the object on which the method was invoked.
Point
class.class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
distanceTo(other) {
return Math.hypot(this.x - other.x, this.y - other.y);
}
}
const origin = new Point(0, 0);
const p = new Point(10, 20);
console.log(p.distanceTo(origin));
We say we’re invoking distance
on p
.
(And passing origin
as the argument.)
(Ooof, no pun intended.)
That’s nice and all, but doesn’t seem like that big a win.
From Greek: “many shaped”
class Circle {
constructor(x, y, r, color) {
this.x = x;
this.y = y;
this.radius = r;
this.color = color;
}
draw(ctx) {
// a bunch of gorp to draw a circle
}
}
class Rectangle {
constructor(x, y, width, height, color) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.color = color;
}
draw(ctx) {
// a bunch of gorp to draw a rectangle
}
}
const shapes = [
new Circle(10, 20, 30, 'black'),
new Rectangle(20, 30, 30, 40, 'black'),
// ...
]
shapes.forEach(s => s.draw(ctx));
Recursion and classes: two great tastes that go great together!
This is based on something Sze Ting is working on where he wants to generate LaTeX (a typesetting language) from mathematical expressions.
class Number {
constructor(value) {
this.value = value;
}
toLaTeX() {
return this.value.toString();
}
}
class Addition {
constructor(left, right) {
this.left = left;
this.right = right;
}
toLaTeX() {
return this.left.toLaTeX() + "+" + this.right.toLaTeX();
}
}
class Subtraction {
constructor(left, right) {
this.left = left;
this.right = right;
}
toLaTeX() {
return this.left.toLaTeX() + "-" + this.right.toLaTeX();
}
}
const expr = new Addition(new Number(2), new Number(3));
const expr2 = new Subtraction(expr, new Number(10));
console.log(expr2.toLaTeX());
These classes are defining something called an Abstract Syntax Tree: a tree of objects that represent the structure of some language, usually for the purpose of transforming that language into another language.
A way of organizing classes and removing a certain amount of duplicate code.
First we define a “base” or “parent” class.
class BinaryOperation {
constructor(left, right) {
this.left = left;
this.right = right;
}
}
Child classes “extend” a parent class and “inherit” behavior and structure from that class.
class Addition extends BinaryOperation {
toLaTeX() {
return this.left.toLaTeX() + "+" + this.right.toLaTeX();
}
}
class Subtraction extends BinaryOperation {
toLaTeX() {
return this.left.toLaTeX() + "-" + this.right.toLaTeX();
}
}
Method dispatch allows us to use the type of the object we invoke the method on to control what code runs.
This is called single dispatch.
But sometimes we want to determine what code to run based on the types of two objects.
Once we’ve got our mathematical expression AST fleshed out, we may want to do something else with it than just generate LaTeX.
We might want to generate some other kind of output. Or we might want to evaluate the expressions.
It’d be nice if we didn’t have to clutter up our AST classes with code for all those different things.
class LaTeX {
number(expr) { return expr.value.toString(); }
addition(expr) {
return this.gen(expr.left) + "+" + this.gen(expr.right);
}
subtraction(expr) {
return this.gen(expr.left) + "-" + this.gen(expr.right);
}
gen(ast) { ast.visit(this); }
}
class Number {
visit(generator) {
return generator.number(this);
}
}
class Addition extends BinaryOperation {
visit(generator) {
return generator.addition(this);
}
}
class Subtraction extends BinaryOperation {
visit(generator) {
return generator.subtraction(this);
}
}
const expr = new Addition(new Number(2), new Number(3));
const expr2 = new Subtraction(expr, new Number(10));
new LaTeX().gen(expr2);
Now we can easily add a new “backend” to generate some other language or do something else with our AST by writing a new class to replace LaTeX
without having to change the AST classes.
class Evaluator {
eval(ast) {
ast.visit(this);
}
number(expr) {
return expr.value;
}
addition(expr) {
return this.eval(expr.left) + this.eval(expr.right);
}
subtraction(expr) {
return this.eval(expr.left) - this.eval(expr.right);
}
}