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.