1

I'm aware there have been a few other questions on this topic, but none of them seem to give a conclusive answer.

I'm building a HTML/CSS/JS mobile app, and have been trying the following style of notation to define some classes:

Style A

var Thing = (function ()
{
    var _instance;

    var _firstName;
    var _lastName;

    function Thing(firstName, lastName)
    {
        _instance = this;

        _firstName = firstName;
        _lastName = lastName;
    }

    Thing.prototype.getMyName = function ()
    {
        return _firstName + " " + _lastName;
    }

    Thing.prototype.speak = function ()
    {
        return ("My name is " + _instance.getMyName());
    }

    return Thing;
}());

The advantages of this are:

  • Member variables are encapsulated and can be referred to directly (e.g. without the this prefix).
  • I can use the _instance variable and therefore avoid ambiguity around the identity of this.
  • The notation is reasonably clean and readable.

I also gave the following alternatives a try:

Style B

function Thing(firstName, lastName)
{
    this._firstName = firstName;
    this._lastName = lastName;
}

Thing.prototype.getMyName = function()
{
    return this._firstName + " " + this._lastName;
};

Thing.prototype.speak = function()
{
    return "My name is " + this.getMyName();
};

Style C

class Thing
{
    constructor (firstName, lastName)
    {
        this._firstName = firstName;
        this._lastName = lastName;
    }

    getMyName ()
    {
        return this._firstName + " " + this._lastName;
    }

    speak ()
    {
        return ("My name is " + this.getMyName());
    }
}

But despite their advantages, I have found B and C difficult to work with because of problems associated with the this keyword; that is, depending on the context of the caller this can refer to different things within the class methods. Furthermore in both these cases, using an _instance variable as I have in A is not possible because all members need to prefixed with this..

However, as pointed out in the comments, Style A does not work when multiple instances of the class are created.

What's the best way to write such classes but avoid problems with this?

Benp44
  • 1,548
  • 1
  • 10
  • 14
  • 2
    Your "Style A" just doesn't work. Have you tried creating multiple instances? Also you never initialise `_instance`. – Bergi Apr 13 '16 at 17:40
  • 2
    Style A is unlike the others. Those variables in the closure are reused for every instance. – elclanrs Apr 13 '16 at 17:40
  • Thanks @Bergi, I've edited to initialise `_instance`. – Benp44 Apr 13 '16 at 17:51
  • @elclanrs - good point, but this means I now have no solution, rather than one. – Benp44 Apr 13 '16 at 17:56
  • 1
    To me it sounds like you're trying to solve the wrong problem. You're trying to avoid using `this`, rather than avoiding using `this` incorrectly. – Kevin B Apr 13 '16 at 18:48

2 Answers2

2

If you want to avoid prototype methods with late-bound this at all costs, you can use the following style in ES6:

class Thing {
    constructor(firstName, lastName) {
        this.getMyName = () => firstName + " " + lastName;
        this.speak = () => "My name is " + this.getMyName();
    }
}

(you can also use a function declaration, but the class has the advantage that it prevents calls without new automatically)

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • But in a non-trivial case where a class has many large methods, containing all of these in the constructor would surely be a bit cumbersome? (although I do recognise the 'lexical this' usage of the arrow functions, which I have been trying unsuccessfully to incorporate into a solution) – Benp44 Apr 13 '16 at 17:48
  • 1
    @Benp44: Well, apparently that is what you want. You cannot have prototype methods without `this`. – Bergi Apr 13 '16 at 17:48
1

You are creating a closure and then sharing the closure among instantiated objects by the Thingconstructor. It will not work as intended.

var elton = new Thing("Elton", "Johnnavartangula");
elton.getMyName(); // <- "Elton Johnnavartangula"
var fenry = new Thing("Fenry", "Honda");
elton.speak(); // <- "My name is Fenry Honda"

sharing privates among instantiated objects is another topic and can be done in several ways like in one of my previous answer or like

function Source(){
var priv = "secret";
return {gp : function(){return priv},
        sp : function(v){priv = v}
       }
}
sharedProto = Source(); // priv is now under closure to be shared
var p1 = Object.create(sharedProto); // sharedProto becomes o1.__proto__
var p2 = Object.create(sharedProto); // sharedProto becomes o2.__proto__
Community
  • 1
  • 1
Redu
  • 25,060
  • 6
  • 56
  • 76