Understanding Objects
Functions are not just first-class objects. They are the basis upon which objects are created in the first place.
In this tutorial, we will discover more about the different types of variables and objects we work with every day in JavaScript. We’ll also explore constructor functions and classes and how they work with the new
keyword.
Learning Outcome Guide
At the end of this tutorial, you should be able to:
- Describe the purpose of the prototype in JavaScript.
- Define a prototype using a constructor function.
- Define a prototype using a class.
- Compare and constrast the terms “function” and “method”.
- Design custom prototypes for objects with properties.
- Design custom prototypes for obejcts with methods.
- Compare and constrast the use of constructor functions and classes when designing prototypes for custom objects.
For the setup of this tutorial, launch VS Code side-by-side with an external terminal. Create a JavaScript file called functions-and-objects.js
and run it in the terminal (node --watch functions-and-objects.js
).
Revisiting Data Types
-
Do you remember some of the different data types we created? Strings, numbers, booleans, dates, and objects? We even created functions as objects. And we discovered that we can use the
typeof
keyword to discover what type a particular variable happened to be.Add the following code to your file. We’ll be using this function to discover more about the variables we are working with in our files.
functions-and-objects.js const describe = function(variable) {console.log(`The value ${variable} is of type ${typeof variable}.`);}let someVariable; // we'll be re-assigning different values to this -
Now, let’s assign our first value to
someVariable
and describe what it is.functions-and-objects.js someVariable = 'Any Text';describe(someVariable);You should see the following in the terminal. Odds are you were able to predict the output before observing it in the terminal.
Terminal window The value Any Text is of type string. -
Let’s add some more code.
functions-and-objects.js someVariable = 42;describe(someVariable);someVariable = true;describe(someVariable);Now the output of our program looks like this.
Terminal window The value Any Text is a string.The value 42 is a number.The value true is a boolean. -
Let’s throw another couple of items in the mix. The first is just an object literal. The second is a reference to our function
describe
.functions-and-objects.js someVariable = {}; // An empty object literaldescribe(someVariable);someVariable = describe; // This is getting very Meta...describe(someVariable);These lines add the following output in our terminal.
Terminal window The value [object Object] is a object.The value function (variable) {console.log(`The value ${variable} is of type ${typeof variable}.`);} is a function.Each time, the template string that we log to the console takes the placeholder
${variable}
and calls its.toString()
to get the text representation of whatever value is inside. For any generic object, its default.toString()
produces[object Object]
. Make note of that distinction.Also note that for any function we create, its
.toString()
will display the function declaration with the code implementation. -
Let’s try assigning another function to
someVariable
.functions-and-objects.js someVariable = console.log;describe(someVariable);These lines add the following output in our terminal.
Terminal window The value function () { [native code] } is of type function.The JavaScript engine is rather protective of what code lays inside of the built-in functions. And that makes sense, at least from a security perspective.
-
Let’s try one more value for
someVariable
. This time, we’ll create an instance of a date.functions-and-objects.js someVariable = new Date('March 5, 2024');describe(someVariable);The interesting thing to note in the display is what the data type is for our
new Date()
:object
.Terminal window The value Tue Mar 05 2024 00:00:00 GMT-0700 (Mountain Standard Time) is of type object. -
As we close out this part of the tutorial, let’s make a change to the
describe()
function.functions-and-objects.js const describe = function (variable) {console.log(`The value ${variable} is of type ${typeof variable}.`);let className = variable.constructor.name;console.log(`The variable is based on the ${className} prototype.\n`)};Underneath all values - primitive or complex - lays some prototype that is used to construct or build the value.
Terminal window The value Any text is of type string.The variable is based on the String prototype.The value 42 is of type number.The variable is based on the Number prototype.The value true is of type boolean.The variable is based on the Boolean prototype.The value [object Object] is of type object.The variable is based on the Object prototype.The value function (variable) {console.log(`The value ${variable} is of type ${typeof variable}.`);let className = variable.constructor.name;console.log(`The variable is based on the ${className} prototype.\n`);} is of type function.The variable is based on the Function prototype.The value function () { [native code] } is of type function.The variable is based on the Function prototype.The value Tue Mar 05 2024 00:00:00 GMT-0700 (Mountain Standard Time) is of type object.The variable is based on the Date prototype.
Even ordinary variables are, in some fashion, objects. The distinction between primitive and complex types still exists (and you can read more about it here). But the key takeaway you should make is that somewhere under the surface is this thing called a Prototype that works to define what an object looks like (its data-holding properties) and how it behaves (its functions or methods).
Constructor Functions
Merriam-Webster defines a “prototype” as “an original model on which something is patterned” (source). In JavaScript, these prototypes are declared using Constructor Functions.
A constructor function is declared like any other function (with function declaration or function expression syntax). What’s different is on the inside (the code in the curly braces) and how it’s called (using the new
keyword). Let’s explore.
-
Imagine we wanted to create objects that represented cars (at least, in the virtual sense). We would start by building a prototype using a constructor function.
functions-and-objects.js const Car = function(vin, color, odometer) {// Coming soon...}You might notice that we declared our function using a capital
C
. It’s common practice to start constructor functions with a capital letter. This is a standard way to help distinguish a constructor function from any other function. We’ve already seen this in regard to theDate()
function;Date()
is a constructor function. (See [naming guidelines](TODO: add reference link).) -
The way we use the constructor function is to prefix the call with the
new
keyword. This should look very similar to how we createdDate()
objects earlier. Let’s build a car!functions-and-objects.js let myFirstCar = new Car('WAUWFAFL9CA035531', 'green', 42);// \_/\___/|||\____/// | | ||| |- Serial Number// | | |||- Plant// | | ||- Model Year// | | |- Check Digit// | |- Vehicle Descriptor Section (VDS)// |- World Manufacturer Identifier (WMI)describe(myFirstCar);Our terminal shows a description of the object. Notice how the name of the constructor function is identified as the object’s prototype.
Terminal window The value [object Object] is of type object.The variable is based on the Car prototype. -
The
describe()
function uses a template string to display the variable. Let’s tweak that function to get a better sense of the data in our objects.functions-and-objects.js const describe = function (variable) {console.log(`The value ${variable} is of type ${typeof variable}.`);console.log("The value is:\n", variable);let className = variable.constructor.name;console.log(`The variable is based on the ${className} prototype.\n`);console.log(`The variable is of type ${typeof variable} and is based on the ${className} prototype.\n`);};The output is a little better. Now, instead of seeing the variable’s value as a
.toString()
representation (which is what happens in template strings), we get to see the actual contents of our object.Terminal window The value is:Car {}The variable is of type object and is based on the Car prototype. -
The parameters of constructor functions are used to supply data that the object can store in its properties. A special keyword is used so that the data is associated with the instance of the object. Let’s modify our
Car
prototype.functions-and-objects.js const Car = function(vin, color, odometer) {this.vin = vin;this.color = color;this.odometerReading = odometer;}Now we can see how our object has retained the data sent in through the parameter list. By storing those parameter values in
this
object, each one becomes a property of the object. We could, for example, accessmyFirstCar.odometerReading
to focus on just the mileage this car has.Terminal window The value is:Car { vin: 'WAUWFAFL9CA035531', color: 'green', odometerReading: 42 }The variable is of type object and is based on the Car prototype. -
Objects aren’t restricted to just properties. We can add methods to our objects, giving them newfound capabilities. Let’s deck out our car some more.
functions-and-objects.js const Car = function(vin, color, odometer) {// Propertiesthis.vin = vin;this.color = color;this.odometerReading = odometer;// Methodsthis.drive = function(distance) {this.odometerReading += distance;}}A method is simply another name for a function that is a member of some object. Here’s what our output looks like.
Terminal window The value is:Car {vin: 'WAUWFAFL9CA035531',color: 'green',odometerReading: 42,drive: [Function (anonymous)]}The variable is of type object and is based on the Car prototype. -
Let’s play with the car, and then make another one.
functions-and-objects.js myFirstCar.drive(75293);console.log(`I sold my first car when it hit ${myFirstCar.odometerReading} km.`);let myDreamCar = new Car('4USCH7333WDWC2JH3', 'silver', 1000);console.log(`I bought my dream car with ${myDreamCar.odometerReading} kliks.`);What’s key to note is that each car is a separate object having its own distinct properties. This can be clearly seen in our output.
Terminal window I sold my first car when it hit 75335 km.I bought my dream car with 1000 kliks.
Constructor functions are the way we create prototypes in JavaScript. There are several helpful characteristics of how object creation (and inheritance) works in JavaScript compared to other languages such as C#. One thing is for certain, however. The popularity of other Object Oriented languages like Java and C# have had a definite effect on JavaScript’s evolution. This is most evident in the emergence of the class
keyword in JavaScript.
Classes
To make JavaScript coding more palatable for developers with backgrounds in other OOP languages, a new keyword and grammar was added. The class
was added as an alternate way to describe object prototypes.
“Classes are a template for creating objects. They encapsulate data with code to work on that data. Classes in JS are built on prototypes but also have some syntax and semantics that are unique to classes.”
Classes on MDN
-
To define a prototype using classes in JavaScript, use the
class
keyword followed by a set of curly braces.const BankAccount = class {constructor (accountNumber, openingBalance) {this.accountNumber = accountNumber;this.balance = openingBalance;}}The curly braces contain the contents of the class. A special function called the
constructor
is invoked when creating a new object based on the class.constructor
is a keyword. -
Using the class to create your object is done exactly the same way you would if the prototype was defined with a constructor function.
let myAccount = new BankAccount("00202-899-123-4567", 500);describe(myAccount);The output when we display our object is also the same. Remember, class syntax in JavaScript is simply an alternate way to declare object prototypes.
Terminal window The value is:BankAccount { accountNumber: '00202-899-123-4567', balance: 500 }The variable is of type object and is based on the BankAccount prototype. -
If we want to define a method in our class, the syntax looks similar to using the constructor. The only difference is that while
constructor
is a keyword, the names we use for our methods can be anything we want. Make the following change to yourBankAccount
class.const BankAccount = class {constructor (accountNumber, openingBalance) {this.accountNumber = accountNumber;this.balance = openingBalance;}deposit(amount) {this.balance += amount;}}Notice that there is no use of the
function
keyword when declaring class methods. Instead, you simply declare the method name, the parameter list, and a body for the method.
Conclusion
This just touches the surface of designing your own types of objects in JavaScript. Prototypes can be created using constructor functions or classes. In this tutorial, you’ve established the basics of object design.
Where to go from here?
At this point, you’ve got enough of a foundation in JavaScript to head out in a number of different directions. You can explore how flow control statements allow us to perform repetitive tasks and choose between alternate paths of logic. Or, you can take a look at how to set up JavaScript projects to build programs using modules and test-driven development (TDD) best practices. You can even progress to working with [JavaScript in HTML][AUTHOR TODO: Link to future tutorial/overview].