To return to my specific example, I think there's a lot of value to be found in subclassing a common Model class to represent each table in my database, especially given the use fo static getters to represent metadata like which columns are the primary keys and how different tables are related.
If you find value in using subclassing in your case, then go for it. I am not going to pretend to be the know-it-all :)
having written classes in Java, Python, Ruby, and JavaScript ...
So we pretty much have the same background :)
what do you see as the most important differences between classes in JavaScript and other languages, and most importantly, what do you see as misleading about them?
I think my biggest complaint about JS classes is the encapsulation. Let's see the following code as example:
As you can see person.talk is not encapsulated as a part of the class Person. Instead, the context is related to where it is called instead. This problem is especially painful when you work with frontend code (e.g. React). You will find the context being mutable, and will be forced to bind them everywhere. So as Alan also mentioned in the other reply, I tend to go with functions and closures in order to workaround this hustle.
Full-time web dev; JS lover since 2002; CSS fanatic. #CSSIsAwesome
I try to stay up with new web platform features. Web feature you don't understand? Tell me! I'll write an article!
He/him
Ah I see. Yeah that's definitely a frustration sometimes, and I've dealt with the React issues you mention. That said though, I wouldn't tie that to JavaScript's class system at all; that's just how objects in general work in JavaScript, and it has its pros and cons. While it's true that it can cause some issues, it also facilitates the composition-over-inheritance style you mentioned above, as it lets you copy methods from one object directly to another without any fancy footwork. Pros and cons, I guess
Interesting for sure! I have never seen this before.
Class Encapsulation seems to imply the name property only for the class object. So the behavior you've shown would be expected because mockElement.onClick is not a Person object. Right?
Indeed changing the assignment to const mockElement = this.person gives proper context when calling mockElement.Talk();
It looks to me like the class object's context is not mutable. That's a good thing.
You can actually manipulate the reference of this. This is what makes it misleading in an OOP perspective because you would have thought the context is part of the encapsulation in the definition. The problem is not strictly coming from javascript classes, but more from the discrepancy of the expected behaviour of how class should be vs how javascript class actually is.
Full-time web dev; JS lover since 2002; CSS fanatic. #CSSIsAwesome
I try to stay up with new web platform features. Web feature you don't understand? Tell me! I'll write an article!
He/him
But I really don't think it has anything to do with classes. I think a better statement would be, "The problem is coming from the discrepancy of the expected behavior of how objects should be vs how javascript objects actually are". IMHO, the confusion is identical using a factory:
I can't see this example being any less confusing than the class example just because we don't use the new keyword. The confusion all boils down to this step:
Regardless of how we built the person object, what's confusing is that the talk method loses its this context when attached to something else.
Now of course, one way to solve this problem is to use purely private vars and closures like you did in your Animal example, but personally, I have one really big problem with that approach: it makes the properties themselves inaccessible. You can no longer do doggo.name = 'Fido' to rename your dog. And hey, If all you need is private vars, go for it, but I don't think this approach covers all cases, or even most.
You can, of course, use a getter and a setter for each public property to make them accessible while keeping the closure and its advantages, but at that point the complexity of the code really ramps up while the readability falls, and personally, I just don't know if it's worth the trade-off:
constAnimal=(name,energy)=>{let_energy=energylet_name=namereturn{getenergy(){return_energy},setenergy(energy){_energy=energy},getname(){return_name},setname(name){_name=name},eat(amount){console.log(`${_name} is eating.`)_energy+=amount},sleep(duration){console.log(`${_name} is eating.`)_energy+=amount},play(duration){console.log(`${_name} is playing.`)_energy-=duration}}}constDog=(name,breed,energy)=>{let_breed=breedreturn{...Animal(name,energy),getbreed(){return_breed},setbreed(breed){_breed=breed},speak(){console.log(`${_name} says, "Woof!"`)}}}
That up there feels like a lot of boilerplate to produce an object with three properties, just so I can occasionally write myBtn.click = myDoggo.speak instead of myBtn.click = () => myDoggo.speak().
This is definitely a personal preference, but I don't think the relatively minor tradeoff of context-free methods is worth it. I personally don't use them nearly often enough to justify that kind of a change across the board. If you do, hey, maybe it's for you, but I personally am so used to JavaScript objects and how functions and this work that it's barely even a frustration, and tbh I just really love the elegance of the class syntax. Unpopular opinion, but IMO it will be even better once the class field and private class field syntaxes become standard.
I think a better statement would be, "The problem is coming from the discrepancy of the expected behavior of how objects should be vs how javascript objects actually are".
I think that is a fair statement. Regardless, that was fun discussion and I think I learnt something from it :)
Full-time web dev; JS lover since 2002; CSS fanatic. #CSSIsAwesome
I try to stay up with new web platform features. Web feature you don't understand? Tell me! I'll write an article!
He/him
If you find value in using subclassing in your case, then go for it. I am not going to pretend to be the know-it-all :)
So we pretty much have the same background :)
I think my biggest complaint about JS classes is the encapsulation. Let's see the following code as example:
As you can see
person.talk
is not encapsulated as a part of the classPerson
. Instead, the context is related to where it is called instead. This problem is especially painful when you work with frontend code (e.g. React). You will find the context being mutable, and will be forced tobind
them everywhere. So as Alan also mentioned in the other reply, I tend to go with functions and closures in order to workaround this hustle.Ah I see. Yeah that's definitely a frustration sometimes, and I've dealt with the React issues you mention. That said though, I wouldn't tie that to JavaScript's class system at all; that's just how objects in general work in JavaScript, and it has its pros and cons. While it's true that it can cause some issues, it also facilitates the composition-over-inheritance style you mentioned above, as it lets you copy methods from one object directly to another without any fancy footwork. Pros and cons, I guess
Interesting for sure! I have never seen this before.
Class Encapsulation seems to imply the name property only for the class object. So the behavior you've shown would be expected because mockElement.onClick is not a Person object. Right?
Indeed changing the assignment to const mockElement = this.person gives proper context when calling mockElement.Talk();
It looks to me like the class object's context is not mutable. That's a good thing.
Actually, class object's context is mutable. Let's take a closer look at the example again:
You can actually manipulate the reference of
this
. This is what makes it misleading in an OOP perspective because you would have thought the context is part of the encapsulation in the definition. The problem is not strictly coming from javascript classes, but more from the discrepancy of the expected behaviour of how class should be vs how javascript class actually is.But I really don't think it has anything to do with classes. I think a better statement would be, "The problem is coming from the discrepancy of the expected behavior of how objects should be vs how javascript objects actually are". IMHO, the confusion is identical using a factory:
I can't see this example being any less confusing than the class example just because we don't use the
new
keyword. The confusion all boils down to this step:Regardless of how we built the
person
object, what's confusing is that thetalk
method loses itsthis
context when attached to something else.Now of course, one way to solve this problem is to use purely private vars and closures like you did in your Animal example, but personally, I have one really big problem with that approach: it makes the properties themselves inaccessible. You can no longer do
doggo.name = 'Fido'
to rename your dog. And hey, If all you need is private vars, go for it, but I don't think this approach covers all cases, or even most.You can, of course, use a getter and a setter for each public property to make them accessible while keeping the closure and its advantages, but at that point the complexity of the code really ramps up while the readability falls, and personally, I just don't know if it's worth the trade-off:
That up there feels like a lot of boilerplate to produce an object with three properties, just so I can occasionally write
myBtn.click = myDoggo.speak
instead ofmyBtn.click = () => myDoggo.speak()
.This is definitely a personal preference, but I don't think the relatively minor tradeoff of context-free methods is worth it. I personally don't use them nearly often enough to justify that kind of a change across the board. If you do, hey, maybe it's for you, but I personally am so used to JavaScript objects and how functions and
this
work that it's barely even a frustration, and tbh I just really love the elegance of theclass
syntax. Unpopular opinion, but IMO it will be even better once the class field and private class field syntaxes become standard.I think that is a fair statement. Regardless, that was fun discussion and I think I learnt something from it :)
Definitely 😁 Thanks to everyone in this thread for the back and forth, it was a good discussion and we made it out without any flames