A subtle error I made with constructors
Martin Graham
Posted on December 30, 2021
In which I almost didn't notice a mistake
I was able to track down a subtle but important error I was making when using constructors with sub-classes and manually setting prototypes.
Consider the following code (from an implementation of rock-paper-scissors):
function Player(){
this.move = null;
}
Player.prototype.setMove = function(mv) {
this.move = mv;
};
function HumanPlayer() {
}
HumanPlayer.prototype = new Player();
let player1 = new HumanPlayer();
let player2 = new HumanPlayer();
player1.setMove('paper');
console.log(player1.move, player2.move);
//paper null
While the error ended up not being a major issue here, notice where the move property of player1 and player2 is initially stored - the object referenced by HumanPlayer.prototype
- and this object is shared by all HumanPlayer
objects!
Both players access their move
property from Human.prototype
- meaning they share this.move
! Let's confirm this by checking the value of move
using getPrototypeOf()
console.log(player1.hasOwnProperty('move'));
//false
console.log(Object.getPrototypeOf(player1).move);
//null
Object.getPrototypeOf(player2).move = 'paper';
console.log(player1.move);
//paper
Not only does player1
not have an own property move, setting player2.[[Prototype]].move
to paper
is accessed by player1.move
! We don't actually have move
defined as individual state for each instance of HumanPlayer
Oddly enough, the program worked fine - consider the setMove()
function:
Player.prototype.setMove = function(mv) {
this.move = mv;
};
When invoking this function using player1.setMove('paper')
, this
refers to player1. Since player1 doesn't have an own property move
, one is created! Each player calls setMove()
, each now has their own move
property, and the move
on HumanPlayer.prototype
is never used again.
player1.setMove('rock');
player2.setMove('paper');
console.log(player1.move, player2.move);
//rock paper
console.log(Object.getPrototypeOf(player1).move);
//null
We got lucky - this time. How to properly fix this?
function Player(){
this.move = null;
}
function HumanPlayer() {
Player.call(this);
}
HumanPlayer.prototype = new Player();
let player1 = new HumanPlayer();
let player2 = new HumanPlayer();
console.log(player1.hasOwnProperty('move'));
//true
Focus on the HumanPlayer
constructor - we've added a call to the Player
constructor. Now creating a new HumanPlayer invokes the Player constructor, (using the context of the object first created due to new
), and sets the move
property on this object. Now each player has their own properties. All is well with the world.
Posted on December 30, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.