DEV Community

Discussion on: My confusions about TypeScript

 
kenbellows profile image
Ken Bellows

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:

const Person = (name) => {
    return {
        name,
        talk(amount) {
            console.log(`${this.name} says hello`)
        }
    }
}

const person = Person('Tom')
const mockElement = {}

mockElement.onClick = person.talk
mockElement.onClick() // this.name -> undefined!

mockElement.conClick = person.talk.bind(person)
mockElement.onClick() // this.name == 'Tom'

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:

mockElement.onClick = person.talk
mockElement.onClick() // this.name -> undefined!

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:

const Animal = (name, energy) => {
    let _energy = energy
    let _name   = name

    return {
      get energy() {
        return _energy
      },
      set energy(energy) {
        _energy = energy
      },

      get name() {
        return _name
      },
      set name(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
      }
    }
  }

  const Dog = (name, breed, energy) => {
    let _breed = breed

    return {
      ...Animal(name, energy),

      get breed() {
        return _breed
      },
      set breed(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.

Thread Thread
 
etampro profile image
Edward Tam
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 :)

Thread Thread
 
kenbellows profile image
Ken Bellows

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