More About Functions!
It's time we had a little chat about functions... in particular, I've been hiding something from you...
Functions as Objects
Functions are objects, can have their own properties assigned to, and can be assigned as values of variables and other objects' properties.
Have you ever stopped and wondered... "Hey, how come we use some methods like console.log() as a function but use the dot-property access like an object?"
Well, I think you just answered your own question!
Take the following brisk examples to see the mechanics of this point:
// Example: Assigning function to variable
let hypotenuse = function (b, h) {
return Math.sqrt(b*b + h*h);
};
console.log(hypotenuse(3, 4));
// Just to drive the point home...
hypotenuse.secretProperty = "shhh!";
console.log(hypotenuse.secretProperty);
(Assigning to properties of functions in this manner is not oft done; we'll look at more common applications shortly.)
Methods
Functions that are values of object properties are referred to as methods.
// Example: Assigning functions to properties
let SetTools = {
combine: function (arr1, arr2) { ... },
difference: function (arr1, arr2) { ... },
// ...
}
// (implementations elided above as { ... })
To call a method, we use a familiar syntax: objName.methodName(arguments);
Modules
Modules like Math are simply objects that collect similarly purposed methods.
Not convinced? Try taking a look at some of the object properties of the Math module!
console.log(typeof Math); // object console.log(Math); // Look at those properties!
Why bother grouping functions as property values into modules like Math? Why not just list all of the functions at the global scope?
A variety of reasons:
Convenient for knowing where to look to find a particular tool that you need ("hmm... I need something Mathy... where should I look for that? Oh right...")
Prevents polution of the global scope with a bunch of different function names (instead, we need only the
Mathobject at the global scope, and then can find all of its individual methods)
...and those are just a couple reasons! We'll see more in the coming sections.
If you want a tag line to help remember this: "All methods are functions, but not all functions are methods." Why is this?
this again!
Last lecture we saw the this keyword in event handlers that allowed us to refer to the DOM object that we were manipulating.
The same is true for designing methods!
When used inside of an object's method, the this keyword returns a pointer to the host object (called the receiver), allowing reference to its other properties.
Let's see how this might be useful!
Design a simple K'tah zombie (...a K'tombie?) that has a name, a health value starting at 100, and then the ability to take damage by some specified amount.
Alright, let's try this naively and then we'll look at a better way to do it later.
let zomB = {
name: "Sir Brainsalot",
health: 100,
// Define a method that removes
// a certain amount of health from
// *this* zombie!
takeDamage: function (damage) {
this.health -= damage;
}
};
A few things to note about the above:
We have *three* properties in our zomB object, but 2 are primitive types (name is a string and health is a number) and the third is a method.
In our method
takeDamage, we parameterize its behavior with some amount of damage to take.We then use the
thiskeyword to indicate that we're talking aboutzomB's health property, on which to subtract the given amount of damage.
So, let's take it for a spin!
// Added after the above definition for zomB: zomB.takeDamage(10); zomB.takeDamage(15); alert(zomB.health); // Should be 75
Pretty neat! Notice how we used the zomB's health property to maintain its health state between calls to takeDamage.
When we use objects to maintain state and then give them instructions to modify that state or provide some sort of feedback, we call this Object Oriented Programming (OOP for short).
Object Oriented Programming is common amongst many programming languages, and we'll now examine in greater depth how it's accomplished in JavaScript.
Add an isDead() method to our zomB object above such that calling zomB.isDead() will return true if its health is at our below 0, false otherwise.
(and no snarky comments about how isDead() should always return true because it's a zombie!)
Classes
So now let's consider a new problem: I want lots of zombies... a veritable cornuk'topia of zombies!
Let's assume (to the contrary) that I have to use the tools I currently possess to mass-produce the zomB object we made above.
What are my options (that we've currently learned of) for mass-producing some number N zomB objects and, say, saving them in an array?
1) Re-declare the object-instantiation {...} syntax N times, one for each new zomB I want (expensive to type). 2) I could try to make 1 zomB object and then
set all of the other array elements = to it (how will this be problematic?). 3) I could use a function to return a new object with the same property values, but how will I know what
properties to copy?
Hmm all of our current tactics have some issues with them... if only we had some tool that could help us perform this replication process...
In traditional JavaScript, we define prototypes that provide a template for individual object creation.
Modern JavaScript (ES6) uses another syntax to specify prototypes called classes, which serve the same purpose.
Once a class is defined, we can create instances of that class, which are the specific, individual objects created using the class "template."
Andrew's Famous Analogy: classes : cookie-cutters :: instances : cookies
Just like we defined functions to be "templates" of behavior, so too can we define classes to be "templates" of objects.
Here is the anatomy of a class definition:
// [!] Class names, by convention, start with a
// capital letter
class ClassName {
// Class methods here
};
Let's take a look at what we can place inside of our class definitions now!
Instance Construction
Recall from above that we want a means of creating individual instances of our classes; we'll look at how to do that now.
Constructors
The contents of a class definition are a non-comma separated enumeration of methods that the class will contain.
A special method called the constructor is invoked whenever we create a new instance of a class. The constructor allows us to initialize some / all of each constructed instance's data properties. A class may have only one constructor.
Let's look at an example using our zomB object from before... now made into class form!
class Zombie {
constructor (name) {
this.name = name;
this.health = 100;
}
}
Let's dissect what this constructor is denoting:
When a user creates a new Zombie instance, they're going to provide it with a name (indicated by the
nameparameter of the constructor).Once the constructor is called, it's going to create a new Zombie instance with the name provided, and starting health of 100. These are the Zombie's data properties since they maintain state about a particular Zombie.
Notice the use of the
thiskeyword in setting the data properties. In a Class' methods (including the constructor)thisrefers to each instance separately.
So now that we have our class "template" for creating new Zombies, let's learn how to create a few!
Creating Instances
To create a new instance of a given class, we use the new keyword. This invokes the constructor of the class we're making an instance of and returns a
new object that abides by the given Class' "template".
The anatomy of an instance creation is as follows:
// Create a new instance of the class ClassName // providing (here 2) arguments to that class' // constructor: let instance = new ClassName(arg1, arg2);
Let's try this with our Zombie class!
// Used with the Zombie class definition above!
// ...
let zom1 = new Zombie("Mike"),
zom2 = new Zombie("Terry");
console.log(zom1);
console.log(zom2);
OK, now let's dissect what we did above!
We created 2 Zombie instances (i.e., two objects that follow the template provided by the Zombie class)
In each new object construction, we provided a single argument designating the name of the particular Zombie we were creating.
The constructor of our Zombie class then set two properties for each instance: health, which is set to 100 by default, and name, which is set to the names we provided via the constructor's parameter.
Draw, using the object and pointer notation, our two new Zombie objects created above.
Now, we're just missing one thing: our takeDamage and isDead methods!
Instance Methods
Adding methods for each of our Zombie instances is relatively simple; we just add them to the class definition!
Let's add the takeDamage method to our Zombie class:
class Zombie {
constructor (name) {
this.name = name;
this.health = 100;
}
// [!] Added takeDamage method
takeDamage (damage) {
this.health -= damage;
}
}
Now, let's try out our new class definition with a couple of instances like before.
// Used with the Zombie class definition above!
// ...
let zom1 = new Zombie("Mike"),
zom2 = new Zombie("Terry");
zom1.takeDamage(10);
console.log(zom1);
console.log(zom2);
Notice a couple things:
Now, both of our Zombie instances are equipped with the
takeDamagemethod.When I call
zom1.takeDamage(10), we reduce the health of Mike by 10 but Terry's health stays (correctly) at 100!
The beauty of classes!
Add the isDead method that you developed above to our ongoing Zombie class.
Practice
Let's try some examples!
Remember good old Disco Clouds? Let's clean that code up with our new knowledge about Classes! Take the Disco Cloud original below and refactor it so that the
cloud objects are now instantiated with a Cloud class from within the setup function.
See the Pen Sun with MORE Disco Clouds (Fixed) by Andrew Forney (@forns) on CodePen.
Click for solution.
See the Pen Sun with MORE Disco Clouds (Classed Up) by Andrew Forney (@forns) on CodePen.
Cool! How about some more practice with a nice classwork exercise?
Remember the Target Practice game we made? Your task is to improve it by doing the following:
Create a
Targetclass that can be used to create the Target instances like the example one given below.Using your new class, change the game to frequently spawn Targets at different
ycoordinates to make a full-on shooting gallery![Bonus +1 CW Credit] Add a score-keeping page element that increments each time the player clicks on a target, and have the targets accelerate in the
xdirection as time goes on!
See the Pen Target Practice by Andrew Forney (@forns) on CodePen.