Constructors and Prototypes (JavaScript)

peytonstrahan

Peyton Strahan

Posted on November 17, 2023

Constructors and Prototypes (JavaScript)

Introduction (Constructors)

Image description

A constructor or constructor function is essentially just a factory function that utilizes the keyword of "new". These types of functions (constructor functions and factory functions) are functions that utilize the same set of code as a sort of "blueprint" to create multiple objects of similar structure. This makes constructors and factory functions very useful tools for pumping out large amounts of similar objects; which can be in the form of items in a video game, profiles that account for each animal in a zoo, etc. Something that can help constructors to do their job is the prototype.

Prototypes
While constructor functions create objects by assigning pre-determined keys to either pre-determined values or values given in a function call, repeatedly giving the same exact key-value pair to different objects can take up more space than necessary. This is where prototypes come in. The prototype is an object that while not usually seen, all objects and constructor functions have as a property by default. By altering the prototype property of the constructor function (NameOfConstructor.prototype), we can create a set of key-value pairs to assign to every object made using the constructor without having to make a unique copy of said key-value pairs for each object. The "prototype" keyword is somewhat useful on its own, but it becomes much more useful and flexible when combined with the "this" keyword.

The Importance of the "this" Keyword
The "this" keyword is used to bind properties to a desired object, allowing you to define the properties of an object from outside of said object. This allows us to not only use the "new" keyword when creating a new instance of an object (which saves a little space by automatically creating an empty object and returning it at the end of your constructor function), but to create methods in the prototype of a constructor function that can carry out processes using referenced properties from whatever objects that were created from said constructor function. This saves space by making it so that the methods are only created once in the code and only take up the same amount of memory as a single instance of those group of methods. Now if all of those words made you confused, maybe a visual example can help you to understand.

Visual Example
To better understand constructors and prototypes, let's take a look at one of the examples of constructor uses that I had stated a while back: "items in a video game". I am going to expand upon this example by referencing a game known as "Terraria". Terraria has many different items in it, with a few able to be seen below:

Image description

The player in this image is hovering over an item called "Godly Megashark", with the "Godly" prefix coming from a modifier that increases the weapon's damage, critical strike chance, and damage. The Megashark is just one of many weapons in the game, with each one having their own base damage, damage type, critical strike chance, speed, and knockback. On top of this, some weapons can have additional statistics such as special effects (50% chance to not use ammo), a unique quote, a size stat, a mana cost stat, and a few other things depending on the weapon. While these additional statistics are more unique to each weapon, both these additional stats and the regular statistics previously mentioned can realistically be applied to all of the game's weapons through a constructor function. The upcoming code example will show you how.

Code Example
While it is nowhere as refined, efficient, or feature-packed as the actual code used in Terraria, I was able to create a rudimentary constructor in JS Bin (Link: https://jsbin.com/zacinut/edit?js,console) that can make most weapons from the game (excluding the land-altering tools that can act as simple melee weapons):

function Weapon(name, damType, dam, critChance, speed, velocity, knock, manaCost, weaponSizePercent, toolTip){
  this.bName = name;
  this.damType = damType;
  this.bDam = dam;
  this.bCritChance = critChance;
  this.bSpeed = speed;
  this.bVelocity = velocity;
  this.bKnock = knock;
  this.bManaCost = manaCost;
  this.bWeaponSizePercent = weaponSizePercent;
  this.toolTip = toolTip;
  this.dName = name;
  this.dDam = dam;
  this.dCritChance = critChance;
  this.dSpeed = speed;
  this.dVelocity = velocity;
  this.dKnock = knock;
  this.dManaCost = manaCost;
  this.dWeaponSizePercent = weaponSizePercent;
}
Enter fullscreen mode Exit fullscreen mode

As you can see, each weapon in Terraria has a lot of stats that it can have. These aren't even all of the stats that each weapon can have, for I didn't even implement the item price, item rarity, and other miscellaneous systems. I did end up giving each item a "type" property of "Weapon":

Weapon.prototype.itemType = "Weapon"
Enter fullscreen mode Exit fullscreen mode

While this property ended up being unused in my code, Terraria has several tools with different purposes that could potentially benefit from having different item-type labels to organize them. Anyways, I also gave each weapon base stats and display stats. The base stats are referenced when using the modifier function to modify the display stats (aka, the actual stats of that specific instance of that weapon):

Weapon.prototype.modifier = function(prefix, dam, critChance, speed, velocity, knock, manaCost, weaponSizeChange){
  if(prefix){
    this.dName = prefix + " " + this.bName;
  }
  this.dDam = this.bDam + (this.bDam * dam);
  this.dCritChance = this.bCritChance + (100 * critChance);
  this.dSpeed = this.bSpeed + (this.bSpeed * speed);
  if(velocity){
    this.dVelocity = this.bVelocity + (this.bVelocity * velocity);
  }
  this.dKnock = this.bKnock + (this.bKnock * knock);
  if(manaCost){
    this.dManaCost = this.bManaCost + (this.bManaCost * manaCost);
  }
  if(weaponSizeChange){
   this.dWeaponSizePercent = this.bWeaponSizePercent + (this.bWeaponSizePercent * weaponSizeChange); 
  }
};
Enter fullscreen mode Exit fullscreen mode

I also implemented a display function that would display the weapon's name and visible stats in a neat manner:

Weapon.prototype.display = function(){
  if(this.manaCost && this.toolTip){
    console.log(`${this.dName}\n${this.dDam} ${this.damType} damage\n${this.dCritChance}% critical strike chance\n${this.dSpeed} speed\n${this.dKnock} knockback\nuses ${this.manaCost} mana\n${this.toolTip}`)
  } else if (this.toolTip){
    console.log(`${this.dName}\n${this.dDam} ${this.damType} damage\n${this.dCritChance}% critical strike chance\n${this.dSpeed} speed\n${this.dKnock} knockback\n${this.toolTip}`)
  } else if (this.manaCost){
    console.log(`${this.dName}\n${this.dDam} ${this.damType} damage\n${this.dCritChance}% critical strike chance\n${this.dSpeed} speed\n${this.dKnock} knockback\nuses ${this.manaCost} mana`)
  } else{
    console.log(`${this.dName}\n${this.dDam} ${this.damType} damage\n${this.dCritChance}% critical strike chance\n${this.dSpeed} speed\n${this.dKnock} knockback`)
  }
};
Enter fullscreen mode Exit fullscreen mode

All of this I did through the use of a lot of code that uses up a lot of space. Now imagine if you had to replicate all of this code for every weapon you wanted to the game, it would be a flood of code! Using a constructor function and some prototypes allowed me to only need to write out that large amount of code once, allowing me to create new weapons without repeating large amounts of code as seen here:

let megaShark = new Weapon("Megashark", "ranged", 25, 4, 7, 10, 1, null, null, "50% chance to save ammo \n'Minishark's older brother'");
megaShark.modifier("Godly", .15, .05, 0, 0, .15, null, null);
megaShark.display();

let terraBlade = new Weapon("Terra Blade", "melee", 85, 4, 18, 12, 6.5, null, 100, null)
terraBlade.modifier("Legendary", .15, .05, .1, 0, .15, null, .1);
terraBlade.display();
Enter fullscreen mode Exit fullscreen mode

Each weapon may have its own copy of its base stats and display stats, but each weapon shares the same exact methods offered by the prototype without needing to make copies of the methods in the memory. The ability to reduce large amounts of space-consuming repetition is one of the main advantages that constructors and prototypes offer.

Conclusion

Image description

Long story short, constructor functions and prototypes are useful tools for creating large amounts of similar objects without creating a lot of repetition or using up a lot of space. Lists of similar items are commonplace in many areas of life and general programming, making these tools useful when creating programs that interact with these areas.

Links to Sources Used
-https://stackoverflow.com/questions/18298435/prototype-and-constructor-in-javascript-plain-english#:~:text=Constructor%20functions%20create%20objects%20and%20assign%20prototypes%20to,%28f.answer%29%3B%20%2F%2F%20%2242%22%20Foo%20is%20a%20constructor%20function.
-https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes
-https://medium.com/@erinlejhimmy/factory-functions-vs-constructor-functions-in-javascript-8bef9510be3c
-https://terraria.fandom.com/wiki/Terra_Blade
-https://terraria.fandom.com/wiki/Megashark
-https://terraria.fandom.com/wiki/Modifiers
-https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
-https://www.w3schools.com/JS/js_object_constructors.asp

💖 💪 🙅 🚩
peytonstrahan
Peyton Strahan

Posted on November 17, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related