Need some help understanding how to extend a JavaScript class

To start, I did read this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends But there is almost nothing there that I understand or recognize enough as something familiar that I can latch onto, so that's not really helpful. All I'm trying to achieve is similar functionality to what this PHP example would do:
<?php // Animal.php
namespace Animals;

class Animal {

public function __construct(protected string $noise){}

protected function makeNoise(): void {
echo ucwords("{$this->noise}!");
}

}

// elsewhere, in a separate file...

<?php // Rat.php
namespace Animals/Mammals;

use Animals/Animal;

class Rat extends Animal {

public function __construct(){
Animal::__construct("squeak");
echo $this->makeNoise(); // Squeak!
}

}
<?php // Animal.php
namespace Animals;

class Animal {

public function __construct(protected string $noise){}

protected function makeNoise(): void {
echo ucwords("{$this->noise}!");
}

}

// elsewhere, in a separate file...

<?php // Rat.php
namespace Animals/Mammals;

use Animals/Animal;

class Rat extends Animal {

public function __construct(){
Animal::__construct("squeak");
echo $this->makeNoise(); // Squeak!
}

}
I'm having a lot of trouble understanding how I'm supposed to make JavaScript work like that, with the shared properties and methods. I figured it'd be pretty similar to set up, but it's clearly not, and I'm really stumped. I have no idea where to start. If anyone could shed some light on this, that would be great. Thanks.
70 Replies
ἔρως
ἔρως2w ago
not gonna lie, you won't find because the php example is not exactly correct Animal::__construct("squeak"); <-- i wouldn't do this, but would do parent::__construct instead which is the same as super() all methods and properties are public, unless you add # then it's private oh, by the way, the php example would need to be an abstract class and the constructor should be private, which you can't do in js
Ray
RayOP2w ago
i mean i pulled something quick out of my ass just to illustrate what i'm trying to make Javascript do, i wouldn't actually use that code for anything
ἔρως
ἔρως2w ago
okay, i guess i went too deep then well, js doesn't have protected properties or methods: either public or private so, yeah, you can't do exactly that
Ray
RayOP2w ago
what i actually have are two classes that are going to need to use nearly all of the same properties and some of the same methods, and it makes the most sense to establish those properties/methods in a base class, but i can't figure out how to extend that base class so the child can use those properties and methods for what it needs to do with them i'm probably explaining that poorly
ἔρως
ἔρως2w ago
just like in php, you should use this. with them as long as they are public
Ray
RayOP2w ago
not what this question is supposed to be about, but what is actually the difference between ClassName::__construct() and parent::__construct() since you've mentioned it? they seem to work identically when extending things
ἔρως
ἔρως2w ago
replace animal with quadrupede_mammal and you will understand it quickly your original version sorta makes an hard dependency on animal and you also need to remember to change the constructor later on but what if you forget?
Ray
RayOP2w ago
OH okay so it's just if i decide i want to change the names of things at a later date, ok, that makes sense
ἔρως
ἔρως2w ago
lets just say you're supposed to call the constructor of your parent, not a specific class what's the parent? it doesn't matter you should get that habbit
Ray
RayOP2w ago
well, the child classes extend a specific parent, so the parent in question does seem like it matters? like, if i change the name of the parent, i would still need to go through and update what the children extend, but that aside if it's best practice to use parent::__construct() then that's what I'm going to start doing that being said though it turns out i couldn't extend my js class, because, for some reason, i did not realize i needed to import it
ἔρως
ἔρως2w ago
yeah that's the use in php
Ray
RayOP2w ago
i thought it was telling me my whatever wasn't defined because i was just extending wrong, turns out, no, it just couldn't find the file
ἔρως
ἔρως2w ago
with an autoloader it is something super weird, in my opinion, but kinda makes sense
Ray
RayOP2w ago
i hate when the problem turns out to be something as basic as "did you link it" lmao makes me feel so dumb
13eck
13eck2w ago
Is this what you're looking for?
// generic animal
class Animal {
// when you create the animal, you need to
// declare the noise it makes
constructor(noise) {
this.noise = noise;
}

makeNoise() {
console.log(this.noise);
}
}

// extension of the base class
class Rat extends Animal {
// note nothing passed to this constructor
constructor() {
// instead, we hard-code
// the noise it makes
super("squeak!");
}
}

const dog = new Animal("woof!");
const rat = new Rat();
dog.makeNoise();

// while not declared on the `Rat` class
// the `.makeNoise()` method is inherited
// from the base `Animal` class
rat.makeNoise();
// generic animal
class Animal {
// when you create the animal, you need to
// declare the noise it makes
constructor(noise) {
this.noise = noise;
}

makeNoise() {
console.log(this.noise);
}
}

// extension of the base class
class Rat extends Animal {
// note nothing passed to this constructor
constructor() {
// instead, we hard-code
// the noise it makes
super("squeak!");
}
}

const dog = new Animal("woof!");
const rat = new Rat();
dog.makeNoise();

// while not declared on the `Rat` class
// the `.makeNoise()` method is inherited
// from the base `Animal` class
rat.makeNoise();
Ray
RayOP2w ago
i might still be dumb though, i need to make sure things are doing what i expect them to do eeeh i feel bad you went ahead and wrote that for me hslfjks thank you; the php example i provided is actually really, really different from what i'm actually trying to do. i was just trying to show that i wanted a base class to establish properties and methods for other classes to use, but discord only lets me have so many characters, and i could not get anything to work because it turned out i just never imported the class from the file it lives in. I thought there was something broken or something I was just entirely missing (it turned out yes but not anything anyone would be able to tell me about based on the information I provided)
13eck
13eck2w ago
Hey, you figured it out ! That's the important thing
Ray
RayOP2w ago
ok yeah i do officially have it figured out, i just finished nestling all the shared items into a base class and finished up roughly half of what i need this upload form to do with no issues. Thank you both for the help, I'm sorry I'm so bad at explaining the problem
ἔρως
ἔρως2w ago
don't worry, the important is that you managed to do what you wanted
StefanH
StefanH2w ago
Not to be stack overflow but since you seem to be new, i'd like to warn you that classes and especially class inheritance is almost always a more complicated solution to something you can solve more elegantly in other ways. If you really need to use inheritance for some reason then go ahead, but especially in javascript, oop isn't very popular and sometimes even seen as an anti-pattern. Since you didn't provide a real code example I don't know if inheritance makes sense for your use case but i find almost always it's not actually needed and just overcomplicates things. So just be weary. I wasted a lot of time trying to solve problems the OOP way until i finally dropped it and became a much better and productive programmer
13eck
13eck2w ago
Since it was brought up, here's a brief tutorial on factory functions and how to use spread syntax to mimic class and inheritance but without needing to use the new keyword (and I avoid this as much as I can as well). With modern JS engines the difference between prototypal inheritance and multiple instances of the same method via factory function is negligible—unless you're writing a game that excpects to run at 60+ FPS then the speed difference becomes important due to framerate considerations.
// factory function that makes an animal
// that makes noise. Note the
// `noise` param being passed when being constructed.
const makeAnimal = (noise) => ({
// note the lack of `noise` being in the returned
// object, this makes it "private" and unaccessible
// outside of the `makeNoise` function
makeNoise() {
console.log(noise);
}
});

// rat factory function
const makeRat = () => ({
// The spread syntax is used to "prime"
// the object with the correct methods.
...makeAnimal("squeek!"),
// added method just to show that you can
// "extend" the object
move() {
console.log("PITTER PATTER!");
}
});

const dog = makeAnimal("WOOF!");
dog.makeNoise(); // output: 'WOOF!'
const rat = makeRat();
rat.makeNoise(); // output: 'squeek!'
rat.move(); // output: 'PITTER PATTER!'
// factory function that makes an animal
// that makes noise. Note the
// `noise` param being passed when being constructed.
const makeAnimal = (noise) => ({
// note the lack of `noise` being in the returned
// object, this makes it "private" and unaccessible
// outside of the `makeNoise` function
makeNoise() {
console.log(noise);
}
});

// rat factory function
const makeRat = () => ({
// The spread syntax is used to "prime"
// the object with the correct methods.
...makeAnimal("squeek!"),
// added method just to show that you can
// "extend" the object
move() {
console.log("PITTER PATTER!");
}
});

const dog = makeAnimal("WOOF!");
dog.makeNoise(); // output: 'WOOF!'
const rat = makeRat();
rat.makeNoise(); // output: 'squeek!'
rat.move(); // output: 'PITTER PATTER!'
Now, since we're using a closure around the noise parameter in the makeAnimal factory function it's not accessible outside that specific function, so if you wanted to be able to use it in the rat function, you'd have to do something like this:
// rat factory function
const makeRat = () => {
const noise = "squeek!";
return {
...makeAnimal(noise),
move() {
console.log("PITTER PATTER!");
},
scurry() {
console.log(`PITTER PATTER goes the rat while it says ${noise}!`
}
}
};
// rat factory function
const makeRat = () => {
const noise = "squeek!";
return {
...makeAnimal(noise),
move() {
console.log("PITTER PATTER!");
},
scurry() {
console.log(`PITTER PATTER goes the rat while it says ${noise}!`
}
}
};
And BOOM! Another closure making noise accessible to the rat function without exposing it to the outside world. Of course, if you don't mind it being accessible/modifiable then you can add it as a property of the makeAnimal return value:
const makeAnimal = (noise) => ({
// now it's a property and can be accessed/modified
// via `animal.noise`
noise,
makeNoise() {
// you can use either `noise` or
// `this.noise` here, since it's a property
// of the object now
console.log(noise);
}
});
const makeAnimal = (noise) => ({
// now it's a property and can be accessed/modified
// via `animal.noise`
noise,
makeNoise() {
// you can use either `noise` or
// `this.noise` here, since it's a property
// of the object now
console.log(noise);
}
});
Or, even better, you can use a getter to get the value but not set it:
const makeAnimal = (noise) => ({
// a getter allows accessing the value
// via `this.noise` but since there's
// no setter you can't modify it
get noise() {
return noise;
},
makeNoise() {
console.log(noise);
}
});

const makeRat = () => ({
...makeAnimal("squeek!"),
move() {
console.log("PITTER PATTER!");
},
moreNoise() {
console.log(`${this.noise} ${this.noise}`)
}
});

const rat = makeRat();
rat.makeNoise(); // output: 'squeek!'
rat.move(); // output: 'PITTER PATTER!'
rat.moreNoise(); // output: 'squeek! squeek!'
const makeAnimal = (noise) => ({
// a getter allows accessing the value
// via `this.noise` but since there's
// no setter you can't modify it
get noise() {
return noise;
},
makeNoise() {
console.log(noise);
}
});

const makeRat = () => ({
...makeAnimal("squeek!"),
move() {
console.log("PITTER PATTER!");
},
moreNoise() {
console.log(`${this.noise} ${this.noise}`)
}
});

const rat = makeRat();
rat.makeNoise(); // output: 'squeek!'
rat.move(); // output: 'PITTER PATTER!'
rat.moreNoise(); // output: 'squeek! squeek!'
And using this method you can chain multiple different object "prototypes" together to create one mega-object! And, since it's using the spread syntax, any method/property of later objects that share the same name as one from an earlier object will overwrite it, so if there's a specific version of a method you need tack it on the end. It's like method overloading, but not :p
ἔρως
ἔρως2w ago
won't this break the principle of substitution? an in, something that takes the result of makeAnimal also must accept the result of makeRat
13eck
13eck2w ago
Not sure how it would break said principle. The makeRat has all the methods of makeAnimal plus a few more -# Though I'm not very well versed in the substitution principle so I could be wrong
ἔρως
ἔρως2w ago
but it isn't an instance of Animal it's just a copy
13eck
13eck2w ago
And? It's still an animal, just not a class-based one It walks like a duck and talks like a duck. Ergo, duck :p
ἔρως
ἔρως2w ago
or a platipus im just asking because the prototype will be different
Ray
RayOP2w ago
Basically i have a handful of products that have varying amounts of spec sheets/installation manuals associated with them in the form of pdfs. I don't want to provide 40+ input sections per pdf type out the gate when the form is initially created because that's incredibly unwieldy and not always necessary, so i needed my javascript to be able to send a fetch request back to PHP for n more slots at will. To do this, it needs the csrf token of course, but it also needs to know which company the current product being built belongs to because each company's product form needs to be configured a little differently, and how many sections are currently already on the page. When i finally properly upload the form after it's filled out, it, again, is going to need the token, the company, and the amount of file slots with something in them, because the plan, what with so much data, is to iterate through the list of one by one, until all the pdfs are in the pdf directory, and the database has everything organized. So the inheritance is from 1 base class that establishes the properties and methods required to call more form sections, and then upload the final data. So like i said, extremely different code in reality, it just wasn't doing anything at first because nothing that should've been inherited was defined and i couldn't figure out why
13eck
13eck2w ago
Frame challenge: have a set 5 or 10 file inputs and require them to submit multiple forms at a time. That way each paylod is smaller and more likely to go through. Sure, it's more work for them, but it also is more resiliant to failure. Trying to upload 40 files at once can be difficult, so you're making them paginate the uploads instead of your code doing it. Also, they can batch the uploads and do a few at a time and come back later to finish
ἔρως
ἔρως2w ago
basically, like a wizard?
13eck
13eck2w ago
Yeah, basically You're a wizard, epic!
ἔρως
ἔρως2w ago
i almost look like hagrid 🤣 but seriously, even if it isn't a wizard, you can make it a lot easier by just asking the basic info, then having an area for the files
13eck
13eck2w ago
Then again, you could have one file input with multiple select enabled:
<form id="file-upload">
<label for="uploads">Select PDFs to upload</label>
<input type="file" multiple accept=".pdf" id="uploads">
<button type="submit">Upload files</button>
</form>
<form id="file-upload">
<label for="uploads">Select PDFs to upload</label>
<input type="file" multiple accept=".pdf" id="uploads">
<button type="submit">Upload files</button>
</form>
ἔρως
ἔρως2w ago
yeah, but the files are uploaded in sequence, instead of you uploading multiple files also, you will hit the upload limit a lot faster instead of the upload limit being by file, it's for all files because of the maximum size of the $_POST variables
13eck
13eck2w ago
That's also true, yeah. So I go back to my prior "set 5 or 10 file inputs"
ἔρως
ἔρως2w ago
or an upload queue and then have 1-4 uploads at a time, maybe handled by a worker actually, no workers needed, but a lot of nasty js code if you want progress
13eck
13eck2w ago
I'm weary of an upload queue because people are dumb. They'll close the window before the queue is empty >_>
ἔρως
ἔρως2w ago
window.onunload = _ => 'there are still files in the queue. are you sure?' there are other ways to do it, like adding a notification at the top showing progress, if possible
Ray
RayOP2w ago
The files also each have links to them that need to be human readable that are usually the associated part's number, a short sentence of info to be displayed with it, and part numbers specific to that manual, that i cant generate from the filename because it's junk, and that all need to be present when they come out the other side on the actual website, so they need to be entered with that pdf; i can't just upload a bunch of files and nothing else. I also very much am planning to have indicators and preventatives in place; part of why i need that input list shared between the two classes is so when the uploader's moved onto the next one, it can also visually remove what's been uploaded already from the page, and mark the current focus as uploading. The only other two people who are going to be using this system besides me absolutely need it lol. Also when i say im doing a file at a time, i do mean I'm doing a queue; the request starts, php handles the post variable, it ends, and js receives a message about it to signal the server is ready for the next, so js makes a new request, php handles that post variable, and so on, until js sees it's at the end, and sends a "done" signal to the server so the server knows to send a finished message to be displayed back when the last one's up. If there's an exception for whatever reason because the server couldn't deal with a file, i still need to figure out what I'll do about it, but I'll approach that later, i just need it to work at all first. There are rollbacks in place. I am already planning to have it send basic info first to get the product entry into the database at all, because it's going to need the database id for that product in order to tie both manual and product together in a junction table. Manuals/sheets are also sometimes shared between products so the junction table is necessary. It's a lot I've had to think about already and I'll likely have to revise something somewhere, but I'll do it as it comes I'm trying to make it work first, and then im adding all the convenience features like visual indicators of progress and such I also keep saying i need them shared; all that's really shared is the property, and the function that sets it. The two child classes are using that property in very different ways, but it's ultimately the same data, i just didn't see a reason to write both the method and the property twice when i could just extend. The parent class also has properties for the csrf token and the company dropdown, which are also used by both children. I didn't want everything in one class because they're technically doing different jobs--one builds and adds to the form, the other sends the inputs off--and when code gets too long it gets really hard to find things
13eck
13eck2w ago
This feels like an X/Y problem: you have X problem and think Y is the solution so you ask about Y instead of X. Do you have a minimal example you can put up on codepen or something so we can see better what you’re after? I think I have about 73% of the picture Then again, you did say above that you figured out it so…maybe not worth the effort :p
Ray
RayOP2w ago
I mean i can make an attempt but all that actually exists and works right now are: - Selecting a company, and receiving that company's product upload form on the change event - entering a number between 1 and 20 in a field, pushing a button, and receiving that many more form fields to upload more entries of that particular pdf type lol Everything else is still just plans in my head, but I've done form handling stuff before, it's just the way im going to be handling the pdfs here specifically that's new (and also class extension in javascript is new too lol but that's been sorted)
13eck
13eck2w ago
For the first item, I would have all the upload forms in the HTML but hidden by default. Use the target pseudo-class to show the correct form. That way there’s no extra processing to do, no extra HTTP calls. It’s all just there. For the second point, I would have a template for the upload fields and an object in memory used to decide, based on the url id, which to use. The JS would then be in charge of populating the correct number of upload fields to the form.
Ray
RayOP2w ago
I should mention, a lot of the choices here are being dictated by the fact this website already exists, it's just a static site with no database at all, and the person who actually owns it wants it to stay as close to what it currently looks like as it can
13eck
13eck2w ago
I’m at work and about to hit the road (Amazon delivery driver) but if you can write out some basic requirements and example upload fields I should be able to knock out a version for you to look at and maybe get inspired by when I get home in ~9hrs
Ray
RayOP2w ago
I'll see what i can do, thank you so much. Also drive safe, idk what your weather is like but my area got slammed with snow this morning, hopefully your roads are fine
13eck
13eck2w ago
Ohio, USA. So yeah, roads are 💩
Ray
RayOP2w ago
Oof, same lol
13eck
13eck2w ago
We should meet up for ☕ then! Warm up lol
Ray
RayOP2w ago
that could be nice lol So this website is for a small business that distributes products for 3 other companies. The first company sells intercom systems for apartment buildings, and emergency/nurse calls for hospitals and retirement homes. The second sells apartment style mailboxes, and the third sells door strikes. So, all very different, but it makes sense to distribute them together. The first company has 3 webpages dedicated to it, one per product type. Each product type has 2-5 different models, and each model has a product brochure and submittal package, a handful (2-10) of installation manuals, and anywhere from 5-40 or so specification sheets. The spec sheets are for the different components that make up the product--so like, speakers, buttons, pull cords, etc. Both manuals and spec sheets are displayed in the same format, with the part number (that's also a link to the manual/sheet), the booklet's topic, and manual number. So: CM800 - Pull cord, 12", with drawing - IL087 Not a real entry, but that's what they look like. Because there are so many sheets, there's an anchor link you can click while viewing the most basic info to jump to the associated spec sheet section toward the bottom of the page. All 3 of this company's product types are organized like this, so they use the same template, but the route tells the server which set of products to put on that template. Or, it will. I have it loading test data right now since the database has nothing in it. The other two companies have significantly less going on; mailbox co sells 4 types of mailboxes, each mailbox has a few captioned photos, some basic product info, and 2 pdf links per box. Door strike co has 3 strike types, but again, there is a varying amount of pdf links, though it's nowhere near as nutty as what's going on with the intercom company. Each strike also comes with a reference table that contains wiring/electrical information. So that... is what I'm trying to sort into some level of sanity rn lol. An alternative solution i've been thinking about is, have a page in the back that's dedicated to uploading PDF files and their associated information, and then upload product info wholly separate, and while on that screen have a bunch of checkboxes or something so you can choose which pdfs belong to that product but that seems like it'd be extremely easy to accidentally neglect adding one, and there are so many I don't think anyone would catch it until customers complain you could argue it'd be easy to accidentally add duplicates too, if you upload PDFs and products together, but if an entry already exists in the database for that pdf, i can use the manual ID to sniff it out and just discard that upload in favor of using the one that already exists I dunno, it's a lot of stuff. It's probably less complicated than I'm making it
Ray
RayOP2w ago
It's hideous at the moment, but this is the current iteration of the "add product" form for the intercom/nurse call/emergency call company. There is one additional menu at the top that's cut off--that is the selection menu for the company. The other two companies don't have forms yet--I'm dealing with this one first since it's the most complex
No description
No description
No description
Ray
RayOP2w ago
I need to change some of those labels, they suck I literally made this whole thing last night, there's a lot that isn't staying the same here, in terms of some of the wording/presentation
Ray
RayOP2w ago
When you first load the page, it looks like this. There's quite literally nothing there until you choose something from the dropdown menu. If you were to choose another company after filling out some of the form for another, it will overwrite the current form, so I will be adding a pop up that asks if you're sure, if i even keep this
No description
Ray
RayOP2w ago
gonna throw a version of the js in a codepen, though it can't work without being able to talk to the server
ἔρως
ἔρως2w ago
not gonna lie, that's very ... dense
Ray
RayOP2w ago
and one of the child classes requires a dependency injection from php yeah, i... i know, aha. i badly want it to be less... that
ἔρως
ἔρως2w ago
i can see someone getting lost in no time
Ray
RayOP2w ago
yeah same, it's something i was going to deal with after i had something that worked at all
ἔρως
ἔρως2w ago
which is fine
Ray
RayOP2w ago
originally i had it sending like, 10 pdf slots to start and when i looked at the actual output i was given i just frowned and immediately turned it down to one each the "ask for more stuff" button is at the bottom of the list, and a new slot is inserted between the div that contains that button, and the last div that contains the existing pdf file upload inputs, so i thought until i think of something better just adding a new slot as you finish entering data would be the best way to go about it
Ray
RayOP2w ago
i hate how bad i am at explaining things; i requested another slot and then turned it yellow--that's where new inputs get added, above the button/input field, but under what already exists
No description
ἔρως
ἔρως2w ago
that is a very meh ui, not gonna lie
Ray
RayOP2w ago
structurally every new field that gets added also has its own ID/labels too; it's not just sending the same piece of html repeatedly
No description
Ray
RayOP2w ago
yeah no i hate it lol, like i said, i made it last night so there's a lot that isn't staying the same, i just need it to work the names in the HTML there probably aren't going to be used, i set that up before i had the realization that sending every PDF at the same time is probably a bad idea the labels are inside the <h4> and <h3> headings, i just realized you can't actually see them
ἔρως
ἔρως2w ago
here's something i whipped out in excalidraw
No description
Ray
RayOP2w ago
I'll see if I can get something that looks like that working, that would definitely be significantly better thank you i figure there's a library somewhere for the file drop part but i'm going to try following the material for that i found on mdn
ἔρως
ἔρως2w ago
oh, yeah, there's lots of bullshit that needs to be done
Ray
RayOP2w ago
So that old mess looks like this new mess now. I couldn't get the drag/drop working because there are two of these upload spots on the page. It works fine when there's just one, but it gets confused and won't let me drop to either of them when there's two, and I understand why it's doing that, but I have absolutely no idea what I'm supposed to do about it. Listening for the change event is probably good enough here anyway; as much as I'd like to fix it, I don't think any of us are going to be doing much dragging/dropping in the first place. Also it is no longer sending requests to the server for more form pieces lol
No description
Ray
RayOP2w ago
I will be adding a little "X" button somewhere on those cards to remove individual files from the queue probably should add a "clear all" button too
ἔρως
ἔρως2w ago
don't forget to add a title attribute, so people can still see the whole name of the file if you do, make sure the user has to confirm the action
StefanH
StefanH2w ago
Yeah really annoying when text cuts off bef...
Ray
RayOP2w ago
Ah yeah that's there! You just can't see it lol

Did you find this page helpful?