0

I'm trying to create a simple javascript inheritance but there's something I missed and I need your help.

Basically, I have a User which can have a username. I also have a Player which inherit from User and can have a score, and play.

var Game = (function () {
  User = function () {
    var username;

    return {
        setUsername: function (newUsername) {
          username = newUserName;
        },
        getUsername: function () {
          return username;
       }
     }
  };

  Player = function () {
    var score = 0;

    return {
      getScore: function () {
        return score;
      },
      play: function () {
        score = Math.round(Math.random()*100);
      }
    };
  };

  Player.prototype = new User();

  return {
    player1: new Player(), 
    player2: new Player()
  };
});

var game = new Game();

game.player1.setUsername('alex');
game.player2.setUsername('tony');

game.player1.play();
game.player2.play();

console.log(game.player1.getUsername()+': '+game.player1.getScore());
console.log(game.player2.getUsername()+': '+game.player2.getScore());

The issue I have comes from the fact I don't have access of the method from the User in my Player. I'm not sure how I can get access to them though.

Here is a jsfiddle: https://jsfiddle.net/3xsu8pdy/

Any idea? Thanks.

Thanks.

alexmngn
  • 9,107
  • 19
  • 70
  • 130
  • Your code throws away the object created by `new`, making using `new` with `Person` or `User` a no-op, it does exactly the same thing as just calling them directly. If you meant using `new` to work, then the question I marked this a duplicate of does indeed have your answer. If not, and you didn't want to use `new`, it doesn't -- in that case, let me know and I'll un-dupehammer it. – T.J. Crowder Jan 08 '16 at 16:31
  • Hmmm, the more I look at the code, the less I think it's answerd there. Un-duping it. – T.J. Crowder Jan 08 '16 at 16:32
  • I believe I'am trying to do something similar to that, but without jQuery: http://stackoverflow.com/questions/15342002/how-to-implement-multiple-inheritance-using-jquery-extend – alexmngn Jan 08 '16 at 16:36
  • Side note: Your code is falling prey to [*The Horror of Implicit Globals*](http://blog.niftysnippets.org/2008/03/horror-of-implicit-globals.html) *(that's a post on my anemic little blog) because you don't declare `User` or `Person`. – T.J. Crowder Jan 08 '16 at 16:50

3 Answers3

2

First, note that you're using new with User and Person, but your code throws away the object new creates by returning a different object from those functions. So new User() and User() do exactly the same thing.

And that's largely the reason you don't have access to User features in Person, because the prototype of the object returned isn't User.prototype, it's Object.prototype.

If you don't want new...

...you want to create your "person" objects so they're backed by User directly (or via Object.create(User())):

var Game = (function() {
    var User = function() { // <== Note the `var`
        var username;

        return {
            setUsername: function(newUsername) {
                username = newUserName;
            },
            getUsername: function() {
                return username;
            }
        }
    };

    var Player = function() {    // <== Note the `var`
        var score = 0;

        // Create the player, either using a User directly:
        var player = User();
        // ...or by using a User as a prototype:
        var player = Object.create(User());

        player.getScore = function() {
            return score;
        };
        player.play = function() {
            score = Math.round(Math.random() * 100);
        };

        return player;
    };

    return {
        player1: Player(),  // No `new`
        player2: Player()
    };
});

var game = new Game();

game.player1.setUsername('alex');
game.player2.setUsername('tony');

game.player1.play();
game.player2.play();

console.log(game.player1.getUsername() + ': ' + game.player1.getScore());
console.log(game.player2.getUsername() + ': ' + game.player2.getScore());

That keeps the username and score properties private, as in your original code.

If you want to use new

...then you probably want the fairly standard pattern I describe in this answer, which looks like this applied to your code:

var Game = (function() {
    var User = function() {
    };
    User.prototype.setUsername = function(newUsername) {
        this.username = newUserName;
    };
    User.prototype.getUsername = function() {
        return this.username;
    };

    var Player = function() {
        this.score = 0;
    };
    Player.prototype = Object.create(User.prototype);
    Player.prototype.constructor = Player;
    Player.prototype.getScore = function() {
        return this.score;
    };
    Player.prototype.play = function() {
        this.score = Math.round(Math.random() * 100);
    };

    return {
        player1: new Player(),
        player2: new Player()
    };
});

var game = new Game();

game.player1.setUsername('alex');
game.player2.setUsername('tony');

game.player1.play();
game.player2.play();

console.log(game.player1.getUsername() + ': ' + game.player1.getScore());
console.log(game.player2.getUsername() + ': ' + game.player2.getScore());

Or in ES2015:

var Game = (function() {

    class User {
        setUsername(newUsername) {
            this.username = newUserName;
        }
        getUsername() {
            return this.username;
        }
    }

    class Player extends User {
        constructor() {
            this.score = 0;
        }
        getScore() {
            return this.score;
        }
        play() {
            this.score = Math.round(Math.random() * 100);
        }
    }

    return {
        player1: new Player(),
        player2: new Player()
    };
});

var game = new Game();

game.player1.setUsername('alex');
game.player2.setUsername('tony');

game.player1.play();
game.player2.play();

console.log(game.player1.getUsername() + ': ' + game.player1.getScore());
console.log(game.player2.getUsername() + ': ' + game.player2.getScore());

Note that in both of those second two examples, username and score are no longer private. That said, even private variables in languages with built-in privacy like Java are trivially used outside of the private scope, via the reflection features of those languages.

In ES2015, we can use a WeakMap to get privacy as good as your original code's is:

var Game = (function() {
    var UserNames = new WeakMap();

    class User {
        setUsername(newUsername) {
            UserNames.set(this, newUsername);
        }
        getUsername() {
            return UserNames.get(this);
        }
    }

    var PlayerScores = new WeakMap();
    class Player extends User {
        constructor() {
            PlayerScores.set(this, 0);
        }
        getScore() {
            return PlayerScores.get(this);
        }
        play() {
            PlayerScores.set(this, Math.round(Math.random() * 100));
        }
    }

    return {
        player1: new Player(),
        player2: new Player()
    };
});

var game = new Game();

game.player1.setUsername('alex');
game.player2.setUsername('tony');

game.player1.play();
game.player2.play();

console.log(game.player1.getUsername() + ': ' + game.player1.getScore());
console.log(game.player2.getUsername() + ': ' + game.player2.getScore());

That doesn't cause a memory leak, because when a User or Person object is no longer referenced by anything other than the WeakMap, the WeakMap lets go of it and it can be garbage collected.

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Why `var User = function() {` when `function User() {` is for all practical purposes identical, and more idiomatic in JavaScript? – ErikE Jan 08 '16 at 18:08
  • @ErikE: It's not "more idiomatic", they're both used quite a lot. I mostly used it because it was what the OP used. In general, I prefer function declarations (your example). There's also some *small* argument for using the expression form (the OP's) in these situations so that everything happens in the same step-by-step flow, since we're modifying prototypes and such (wouldn't matter here, though). – T.J. Crowder Jan 08 '16 at 18:45
  • I meant idiomatic as "suited to the language's vernacular as seen by long experts" rather than "most common". The fact that the OP used `User = function()` to me exposes the problem inherent in using the `var` construction—people begin to lose understanding of what they're actually doing. In my experience, people who use the `var` construction often don't even know it's equivalent to the standard `function` construction, and this is a problem (in my eyes). Encouraging the standard `function` use is of value, I think. – ErikE Jan 08 '16 at 18:47
  • @ErikE: Function declarations are no more (or less) "suited to the vernacular as seen by long experts" (not sure what a "long expert" is, but I'm guessing I qualify) than function expressions. It totally depends on what you're doing, whether you want it hoisted, etc. In fact, I've know JS experts who *never* used declarations, because they weren't in the step-by-step flow. – T.J. Crowder Jan 08 '16 at 18:50
  • I agree, there are uses for `var`. As for which is "better" in standard cases, I guess that's a matter of opinion. I'm sure you qualify as a "long expert" and you did say you prefer function declarations. That's all I mean! Function declarations win in conciseness. They also win when you don't need hoisting and when the runtime assignment of the function is not of import, because then there is no false signal where another developer sees your code and has to check, wait, is there a reason this is being assigned to a `var` instead of being a `function`? Unusual code should do unusual things! – ErikE Jan 08 '16 at 19:01
  • I respect your opinion on that! `function A() {` seems superior to me over `var A = function() {`. It's definitely less straightforward. Code should be 1) correct, 2) clear, and 3) concise, in that order. Those constructions are equally correct, but the latter is less concise and arguably less clear (when the time of assignment doesn't matter). So that's my story and I'm sticking to it! – ErikE Jan 08 '16 at 19:31
0

Try this:

User = function () {};

User.prototype.setUsername = function (newUsername) {
  this.username = newUserName;
};

User.prototype.getUsername = function () {
  return this.username;
};

Same for Player :-)

The following code is a variation of this pattern. The extend() function takes care of binding prototypes together. The sup parameter refers to the parent prototype. Though, I'm afraid that it's old fashioned, I'm not sure if it's a good idea to go this way. Anyway, I believe that it's easier to understand than the previous code.

var A = extend(Object, function (sup) {
  this.init = function (name) {
    this.name = name;
  };
  this.whoami = function () {
    return this.name;
  };
});

var B = extend(A, function (sup) {
  this.whoami = function () {
    return 'I\'m ' + sup.whoami.call(this) + '.';
  };
});

var C = extend(B, function (sup) {
  this.init = function (name) {
    sup.init.call(this, '&star; ' + name + ' &star;');
  };
});

var a = new A('an instance of A');
var b = new B('an instance of B');
var c = new C('an instance of C');

log(
  'a.whoami()',
  'b.whoami()',
  'c.whoami()',
  'b instanceof A',
  'b instanceof B',
  'b instanceof C'
);

function extend (tail, head) {
  function Class () {
    this.init.apply(this, arguments);
  }
  head.prototype = tail.prototype;
  Class.prototype = new head(tail.prototype);
  Class.prototype.constructor = Class;
  return Class;
}
<table><thead><tr><th>code</th><th>result</th></tr></thead><tbody></tbody></table><script>function log(){var el=document.getElementsByTagName('tbody')[0];Array.prototype.slice.call(arguments).forEach(function(x){el.innerHTML+='<tr><td>'+x+'</td><td>'+eval(x)+'</td></tr>';});}</script><style>table,td,th{text-align:left;border:1px solid #333;border-collapse:collapse;padding:.5em;}</style>
0

I see where you are trying to go with the revealing module design pattern but If you are going for inheritance, you should be going with constructors. Something like:

var module = (function () {
    function User () {
        this.username = 'default';
    }
    User.prototype.setUsername = function (username) {
        this.username = username;
    };
    User.prototype.getUsername = function () {
        return this.username;
    };

    function Player () {
        User.call(this);
        this.score = 0;
    }
    Player.prototype = Object.create(User.prototype);
    Player.prototype.getScore = function () {
        return this.score;
    };
    Player.prototype.play = function () {
        this.score = Math.round(Math.random()*100);
    };

    return {
        player1 = new Player(),
        player2 = new Player()
    };
}());

So we are still using the revealing module pattern but we also get prototypal inheritance and all of the optimizations that come with it. This is what I would recommend.

Vikk
  • 617
  • 7
  • 17
  • In JavaScript, it's perfectly valid to do inheritance without using constructor functions. – T.J. Crowder Jan 08 '16 at 16:50
  • That's correct T.J., but constructor functions are the best convention we have until es6 classes are fully supported. They are clean, readable, and straight forward. The only other pattern that comes close is the Decorater pattern. – Vikk Jan 08 '16 at 16:55
  • Vikk: I think you'd find that there's a large body of people out there (lead by Douglas Crockford) who vehemently disagree with that claim. Most prototypical languages don't have constructor functions at all; that JavaScript does is a bit unusual. (That said, personally, in JavaScript I prefer using constructors to those other approaches for when I'm doing a factory for several initially-identical objects, saving `Object.create` and such for one-off situations. But that's just me; Crockford and his adherents completely eschew `new`.) – T.J. Crowder Jan 08 '16 at 16:59
  • I've gotta thank you for getting me to look into the constructors a bit more. It did NOT work how I thought it did. The problem isn't necessarily with constructors it would seem, it's with "new" and the baggage that comes with constructors. Instanceof and the constructor property are completely misleading. I am definitely switching to factory functions for object creation after a proper amount of reeducation. – Vikk Jan 08 '16 at 18:27
  • LOL! Wasn't trying to create a convert! :-) FWIW, I think constructor functions are just fine, but it's really good to know the details of what you're working with, so you can pick the right tool (whether it's a constructor function, a factory/builder/creator function, even just an inline `Object.create`, etc. I use constructors a lot, but sometimes they're just not the right tool. Kudos on open-mindedness, so rare!! – T.J. Crowder Jan 08 '16 at 18:47