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:

  1. 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...")

  2. Prevents polution of the global scope with a bunch of different function names (instead, we need only the Math object 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 this keyword to indicate that we're talking about zomB'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 name parameter 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 this keyword in setting the data properties. In a Class' methods (including the constructor) this refers 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 takeDamage method.

  • 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 Target class 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 y coordinates 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 x direction as time goes on!

See the Pen Target Practice by Andrew Forney (@forns) on CodePen.



  PDF / Print