Reputation: 9627
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.
Upvotes: 0
Views: 190
Reputation:
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, '☆ ' + name + ' ☆');
};
});
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>
Upvotes: 0
Reputation: 1075119
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
.
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.
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.
Upvotes: 2
Reputation: 647
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.
Upvotes: 0