I have a vanilla Javascript class that builds a bunch of HTML, essentially a collection of related HTMLElement objects that form the user interface for a component, and appends them to the HTML document. The class implements controller logic, responding to events, mutating some of the HTMLElements etc.
My gut instinct (coming from more backend development experience) is to store those HTMLElement objects inside my class, whether inside a key/value object or in an array, so my class can just access them directly through native properties whenever it's doing something with them. But everything I look at seems to follow the pattern of relying on document selectors (document.getElementById
, getElementsByClassName
, etc etc). I understand the general utility of that approach but it feels weird to have a class that creates objects, discards its own references to them, and then just looks them back up again when needed.
A simplified example would look like this:
<html>
<body>
<script>
/* Silly implementation of the "Concentration" memory match game.
This is just a S/O example but it should actually work =P
*/
class SymbolMatchGame {
constructor(symbolsArray) {
this.symbols = symbolsArray.concat(symbolsArray); // we want two of every item
this.allButtons = [];
this.lastButtonClicked = null;
}
shuffle() {
for (let i = this.symbols.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = this.symbols[i];
this.symbols[i] = this.symbols[j];
this.symbols[j] = temp;
}
}
build(parentElement) {
document.body.innerHTML = '';
this.shuffle();
const rowSize = Math.floor(Math.sqrt(this.symbols.length));
for (let i = 0; i < this.symbols.length; i++) {
const button = document.createElement('input');
button.type = 'button';
button.setAttribute('secret-value', this.symbols[i]);
button.value = ' ';
button.onclick = (event) => this.turnOver(event);
this.allButtons.push(button);
document.body.appendChild(button);
if ((i+1) % rowSize === 0) {
const lineBreak = document.createElement('br');
document.body.appendChild(lineBreak);
}
}
}
turnOver(event) {
const button = event.target;
if (this.lastButtonClicked === null) {
this.allButtons.forEach(button => button.value = button.disabled ? button.value : ' ');
this.lastButtonClicked = button;
} else if (button === this.lastButtonClicked) {
button.value = ' ';
this.lastButtonClicked = null;
} else {
if (button.getAttribute('secret-value') === this.lastButtonClicked.getAttribute('secret-value')) {
console.log('Match found!');
button.disabled = true;
this.lastButtonClicked.disabled = true;
} else {
console.log('No match!');
}
this.lastButtonClicked = null;
}
button.value = button.getAttribute('secret-value');
if (this.gameIsSolved()) {
alert('You did it! Game will reset.')
this.build();
}
}
gameIsSolved() {
const remainingButtons = game.allButtons.filter(button => !button.disabled)
return remainingButtons.length === 0;
}
}
const alphabetArray = Array.from(Array(8).keys()).map(k => String.fromCharCode(k+65));
game = new SymbolMatchGame(alphabetArray);
game.build();
</script>
</body>
</html>
(Note: I'm not expecting you to examine this code in detail; just illustrating what I mean when I talk about storing element references in the class and accessing them directly instead of via document.get*
lookups)
I don't want this to be a style/"best practice" question that isn't appropriate for S/O, so I'm more looking for concrete information about whether my approach actually works the way I think it does. My question: what are the implications or side effects of what I'm doing? Is storing references to created elements inside my class instead of of a "just in time" document.get*
lookup every time I want to access or modify them unsafe in some way, prone to side effects or stale references, or otherwise lacking implicit guarantees about document state, that might break things on me?