Ideally I would like to do something like the following:
class Chess = {
constructor() {
this.board = ...;
...
};
class Square = {
constructor(row, col) {
this.row = row;
this.col = col;
};
};
My main motivation is that with the Chess and Square classes defined separately something like: (this referring to the Chess class)
this.empty(square)
could be shortened to
square.empty()
which is more readable and more concise.
Unfortunately I can't just make a
Square.empty()
method since the results depends on the information in the Chess class and
square.empty(chess)
is no real improvement.
The reason I have a Square class is that something like
square.up()
seems much nicer than something like
[row, col + 1]
Do you have a suggestion on how I would accomplish the above? Some way to write a class within a class or something else entirely?
EDIT:
Following the advice from likle and alex I did the following:
I added a context property to the Class Square
class Square = {
constructor(context, row, col) {
this.context = context;
this.row = row;
this.col = col;
};
};
Then redefined some methods from the Chess.prototype to the Square.protoype. For example:
// before
Chess.prototype.empty = function (square) {
return this.piece(square) === 0;
};
// after
Square.prototype.empty = function () {
return this.piece() === 0;
};
Which meant that every time I created a Square object I need to add context. For example:
new Square(3, 4); // before
new Square(this, 3, 4); // after
new Square(this.context, 3, 4); // sometimes like this
To make the code more readable I created the following method:
Chess.prototype.createSquare = function (row, col) {
return new Square(this, row, col);
};
So a Square object can sometimes be created with
this.createSquare(3, 4);
Ideally I would like to do something like the following:
class Chess = {
constructor() {
this.board = ...;
...
};
class Square = {
constructor(row, col) {
this.row = row;
this.col = col;
};
};
My main motivation is that with the Chess and Square classes defined separately something like: (this referring to the Chess class)
this.empty(square)
could be shortened to
square.empty()
which is more readable and more concise.
Unfortunately I can't just make a
Square.empty()
method since the results depends on the information in the Chess class and
square.empty(chess)
is no real improvement.
The reason I have a Square class is that something like
square.up()
seems much nicer than something like
[row, col + 1]
Do you have a suggestion on how I would accomplish the above? Some way to write a class within a class or something else entirely?
EDIT:
Following the advice from likle and alex I did the following:
I added a context property to the Class Square
class Square = {
constructor(context, row, col) {
this.context = context;
this.row = row;
this.col = col;
};
};
Then redefined some methods from the Chess.prototype to the Square.protoype. For example:
// before
Chess.prototype.empty = function (square) {
return this.piece(square) === 0;
};
// after
Square.prototype.empty = function () {
return this.piece() === 0;
};
Which meant that every time I created a Square object I need to add context. For example:
new Square(3, 4); // before
new Square(this, 3, 4); // after
new Square(this.context, 3, 4); // sometimes like this
To make the code more readable I created the following method:
Chess.prototype.createSquare = function (row, col) {
return new Square(this, row, col);
};
So a Square object can sometimes be created with
this.createSquare(3, 4);
Share
Improve this question
edited Nov 24, 2018 at 12:19
David
asked Nov 23, 2018 at 14:01
DavidDavid
1381 gold badge1 silver badge9 bronze badges
1
|
3 Answers
Reset to default 11One may argue that JavaScript does not have "nested" classes as such -- there is no way for a class to use parent class scope, for instance, nor would it be meaningful for anything but accessing parent class properties (static
elements).
A class in JavaScript is just an object like any other, so you may as well define one and refer to it with a property on another class:
class Chess {
}
Chess.Square = class {
};
(yes, a name for a class is optional -- above, the Square
property on Chess
refers to a class without name; not necessarily great for debugging and introspection, but just illustrating a point here)
Having the above, you can do things like:
new Chess.Square();
And generally everything else you do with a class or objects of a class -- new
simply expects a function that will act as a constructor, and class
declarations actually "decay" into constructor functions when program is run -- values of all of the following expressions are true:
Chess instanceof Function
typeof Chess == "function"
Chess.Square instanceof Function
typeof Chess.Square == "function"
Chess.prototype.constructor == Chess
Chess.Square.prototype.constructor == Chess.Square
There is some extra metadata associated with every class by ECMAScript -- compared to actually using a function as constructor (the older, ECMAScript 5 notation before class
keyword was introduced), and some differences, but it has no bearing on being able to nest constructors to access outer scope:
function Chess() {
const chess = this;
function Square() { /// Obviously, this function/constructor/class is only available to expressions and statements in the Chess function/constructor/class (and if explicitly "shared" with other code).
Chess; /// Refers to the outer constructor/class -- you can do `new Chess()` etc
this; /// Refers to this Square object being created
chess; /// Refers to what `chess` declared outside this method, refers to at the time of creating this Square object
}
}
The above uses what is known as "closures", which kind of require you to have a good grasp on how scoping works in JavaScript. In Java, the Square
class as specified above, would be known as a nested instance class, as opposed to a nested static class. The first example at the very top of the answer, using class
keyword, does specify a form of the latter, though (a "static" class).
Here is yet another way to define an "instance" class Square
:
class Chess {
constructor() {
this.Square = class {
constructor() {
/// Be vigilant about `this` though -- in every function (except arrow functions) `this` is re-bound, meaning `this` in this constructor refers to an instance of the anonymous class, not to an instance of the `Chess` class; if you want to access the latter instance, you must have a reference to it saved for this constructor to access, e.g. with the `const chess = this;` statement in `Chess` constructor somewhere
}
}; /// Every `Chess` object gets a _distinct_ anonymous class referred to with a property named `Square`; is a class for every object expensive? is it needed? is it useful?
}
}
The thing with JavaScript is that it, for better or worse, makes it possible to implement OOP in a bit more than one way, certainly more so than some of the other "OOP languages" allow, and "nested classes" can mean different things, to different people, and in different programs. This is known as "having a lot of rope to hang yourself with" -- a lot of facility that may either help you implement your intended model elegantly, and/or make your code hard to read (especially to people who don't know as much JavaScript as you) and debug. It's a trade-off one needs to address before going all in with "nested classes".
Currently, there are no nested classes. What you could do is to have to two separate classes, Chess
and ChessSquare
- and have a reference to the chess passed in the constructor of the ChessSquare
and keep that stored as a property. This way you won't have to pass it in the methods of the ChessSquare
:
class ChessSquare = {
constructor(chess, row, col) {
this.chess = chess;
this.row = row;
this.col = col;
}
empty() {
// "this.chess" references the chess, and "this" references the square.
}
};
You probably want to create all instances of ChessSquare
within the Chess
class itself.
The following piece of code works perfectly well:
class Chess {
constructor() {
this.board = {}
for (var i=0; i<8; i++) {
for (var j=0; j<8; j++) {
this.board[i,j] = new Square(i,j)
}
}
};
};
class Square {
constructor(row, col) {
this.row = row;
this.col = col;
this.color = "black"
if ((row+col)%2 == 1) this.color = "white"
};
};
The two separate classes are interlinked within the Chess (main?) class with direct call to the Square class, so that at Chess instantiation time, the board squares are also defined.
Chess
instantiate instances ofSquare
. – alex Commented Nov 23, 2018 at 14:02