0

I’ve been playing with classes and subclasses in Javascript, and I thought I’d try what seems to be a simple example: a Circle class which is initialized with cx, cy, (coordinates of the origin) and r (the size of the radius). I also pass in a reference to an SVG. Then the Circle draws itself upon instantiation, like this:

let c = new Circle(svg, 100, 100, 10);

So far so good, this class definition works:

class Circle {
  constructor(svg,r,cx,cy){
    this.svg = svg;
    this.circle = document.createElementNS("http://www.w3.org/2000/svg", "circle"); 
    this.circle.setAttribute("cx", cx);
    this.circle.setAttribute("cy", cy);
    this.circle.setAttribute("r", r);

    this.draw()
  }
  draw(){
    this.svg.appendChild(this.circle);
  }
}

Now for subclassing. I figured a good place for a .random(n) method would be on the RandomCircle class, so I tried this:

class RandomCircle extends Circle {
  constructor(svg){
    cx = this.random(svg.getAttribute('width')) // error
    cy = this.random(svg.getAttribute('height')) // error
    r = this.random(10); // error
    super(svg,cx,cy,r);
    this.draw();
  }
  random(n){
    return Math.floor(Math.random()*n) 
  }
}

Looks pretty good, doesn’t work, apparently because you can’t refer to this before calling super. So my quandary is, what do I do if I want to use class methods to calculate parameters to pass to super() in a derived class?

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>random circles</title>
</head>
<body>

<svg style="border:1px solid black" width=500 height=500></svg>

<script>
class Circle {
  constructor(svg,r,cx,cy){
    this.svg = svg;
    this.circle = document.createElementNS("http://www.w3.org/2000/svg", "circle"); 
    this.circle.setAttribute("cx", cx);
    this.circle.setAttribute("cy", cy);
    this.circle.setAttribute("r", r);

    this.draw()
  }
  draw(){
    this.svg.appendChild(this.circle);
  }
}

class RandomCircle extends Circle {
  constructor(svg){
    cx = this.random(svg.getAttribute('width')) // error
    cy = this.random(svg.getAttribute('height')) // error
    r = this.random(10); // error
    super(svg,cx,cy,r);
    this.draw();
  }
  random(n){
    return Math.floor(Math.random()*n) 
  }
}

let svg = document.querySelector('svg');
Array(3).fill().forEach(() => new Circle(svg, 10,Math.floor(Math.random()*500),Math.floor(Math.random()*500))); // these are fine
new RandomCircle(svg); // this fails
</script>

</body>
</html>
  • My main question is, why is it a class method? For this usecase at least, that function can easily be a standalone function. – loganfsmyth Nov 04 '16 at 19:02
  • Perhaps so, in this use case. But surely at some point using class methods to calculated constructor parameters for extended classes will be useful. –  Nov 04 '16 at 19:04
  • Methods have access to instance properties, which supposedly are not initialized before the constructor is called - this is one argument against using class methods in this case. – artem Nov 04 '16 at 19:13
  • Calling a class method in a constructor is a edge casey thing many languages. See http://stackoverflow.com/a/5230637/785065 and https://softwareengineering.stackexchange.com/questions/48932/constructor-should-generally-not-call-methods Ideally you want to keep your constructor logic to a minimum. – loganfsmyth Nov 04 '16 at 19:38

1 Answers1

0

Use the new ES2015 setter and getter methods. Also super() has to be the first method call in the constructor method.

class Circle {
  constructor(cx) {
    this.cx = cx;
    …
  }

  get cx() {
    return this.cx;
  }

  set cx(cx) {
    this.cx = cx;
  }

}

class RandomCircle extends Circle {
  constructor() {
    super();
    this.cx = random(10);
  }

  random(n) {
    …
  }
}

Here is a great video tutorial about ES2015 Classes: https://www.youtube.com/watch?v=EUtZRwA7Fqc

I also recommend "The Principles of Object-Oriented JavaScript " by Nicholas C. Zakas https://www.amazon.com/Principles-Object-Oriented-JavaScript-Nicholas-Zakas/dp/1593275404

marcobiedermann
  • 4,317
  • 3
  • 24
  • 37