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.

4
  • 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. Commented Jan 8, 2016 at 16:31
  • Hmmm, the more I look at the code, the less I think it's answerd there. Un-duping it. Commented Jan 8, 2016 at 16:32
  • I believe I'am trying to do something similar to that, but without jQuery: stackoverflow.com/questions/15342002/… Commented Jan 8, 2016 at 16:36
  • Side note: Your code is falling prey to The Horror of Implicit Globals *(that's a post on my anemic little blog) because you don't declare User or Person. Commented Jan 8, 2016 at 16:50

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

6 Comments

Why var User = function() { when function User() { is for all practical purposes identical, and more idiomatic in JavaScript?
@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).
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: 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.
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!
|
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.

5 Comments

In JavaScript, it's perfectly valid to do inheritance without using constructor functions.
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: 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.)
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.
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!!
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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.