wp-content/uploads/2016/01/PBS_Logo.png

In this instalment we’re going to continue to consolidate our understanding of JavaScript classes by improving the Cellular Automaton classes we built together in previous instalments. This time we’ll make a start on improving how the classes represent and deal with cell states. The challenge will be to finish the task.

We’ll also take some time to revise the basics of JavaScript objects.

The ZIP file for this instalment contains my sample solution to the previous challenge.

Note that this instalment is split over two podcast episodes, only one has been recorded to date.

Listen Along (Part 1 of 2): Chit Chat Accross the Pond Episode 521

Listen Along (Part 2 of 2): Chit Chat Accross the Pond Episode 522

Challenge Solution

At the end of the previous instalment I set a challenge based on our farm from the previous challenge. Six changes were requested, so let’s go through them one by one. Reminder, the full source code for my sample solution is in this instalment’s ZIP file.

Part 1 — Add a .species() function to Animal

The first part of the challenge was simply to add an instance function named .species() to the Animal class that will return the name of the Animal’s class as a string. Based on what we learned about the .constructor and .name properties last time, this is a very short little function indeed:

We can test our function by opening index.html in our favourite browser and entering bartFarm._animals[0].species() in the JavaScript console. It should return the species of the first animal in the farm – 'Cow'.

Part 2 — Add a .speciesInventory() function to Farm

The second part of the challenge was to make use of the .species() function we just added to Animal to add an instance function to the Farm class named .speciesInventory() which will return a plain object where the keys are species names, and the values head-counts for that species.

This function is a little longer, but not much more complicated. They key is that instances of the Farm class store their list of animals in an instance variable named ._animals that’s an array. We simply need to loop over this array and count how many of each species we meet:

Again, we can test our function by entering bartFarm.speciesInventory() into the JavaScript console.

Part 3 — Add a .farm_inventory <div> to the Farm

The third part of the challenge was extremely simply — update the Farm class’s constructor so it creates an empty <div> with the class farm_inventory:

Part 4 — Show the Inventory

The fourth part of the challenge was to add code to the .addAnimal() function in the Farm class to render the current inventory each time an animal is added.

This involves calling the function from part 3, and then using jQuery to build and inject DOM elements:

Part 5 — Add a Static isAnimal() to Animal

The penultimate part of the challenge was to add a static function named isAnimal() to the Animal class which expects one argument, and returns true if that argument is an instance of Animal or any subclass there-of, and false otherwise.

They key here is to understand that the instanceof operator is aware of inheritance, and will follow the inheritance tree. In our farm example, an instance of Cow would be an instance of Animal because the class Cow extends the class Animal. Similarly, an instance of Duck would also be an instance of Animal because Duck extends EggLayer which extends Animal.

Once you understand that, the function becomes very easy to write indeed:

We can use the JavaScript console to test this function:

  • Animal.isAnimal(bartFarm._animals[0]) should return true
  • Animal.isAnimal('boogers') should return false

Part 6 — Add a Static areSameSpecies() function to Animal

The final part of the challenge is to add another static function to the Animal class, but this one should be named areSameSpecies(). As the name suggests, this function should take two arguments, and only return true is both are animals of the same species.

The only small complication is that you need to be a little careful in how you structure your tests so as to avoid generating errors when passed non-objects to test. The key is to first make sure both arguments are instance of the Animal class using the static function we created in step 5 before calling the .species() instance function on both arguments and comparing the results:

Again, we can use the console to test our code:

  • Refresh the page to get a default farm where the first animal is a cow and the second a duck, then the following should evaluate to false: Animal.areSameSpecies(bartFarm._animals[0], bartFarm._animals[1])
  • Now add a cow as a 5th animal and the following should evaluate to true: Animal.areSameSpecies(bartFarm._animals[0], bartFarm._animals[4])

You’ll find my full sample solution in a folder named pbs48ChallengeSolution in the ZIP file for this instalment.

Note: This is the point in the notes where the first podcast episode ends and the second will begin.

Revision — JavaScript Object Basics

A JavaScript object is a collection of key-value pairs. You’ll also see the keys are often referred to as names, so name-value pair is synonymous with key-value pair. Each key-value pair is referred to as a member of the object.

The keys can, in theory, be any JavaScript string. If you try use a key that’s not a string, like a number, it will be automatically converted to a string before use. So, when you try to access the key 1, you are actually accessing the key "1". I don’t know why you’d want to, but you can use the empty string as a key within an object!

Objects are namespaces. That is to say, they are self-contained universes of names. If you have two objects, obj1 & obj2, then the key x within obj1 is completely unrelated to the key x within obj2.

Object Literals

The object literal syntax lets you create an object and its members in one go, the syntax is as follows:

The keys are the items to the left of the colon, and the values the items to the right. The values can be primitives like numbers or strings, or they can be references to objects. Since just about everything in JavaScript is an object, that means you can store references to things like arrays, functions, or even regular expressions within objects.

If a key is a valid JavaScript variable name (as described way back in instalment 12), then it doesn’t have to be quoted but if the key contains even a single character that can’t appear in a variable name then it must be quoted. So, we can re-write the above sample as:

Notice that "key 6" and "key-7" are still quoted, because neither spaces nor dashes are permitted within variable names.

In reality, keys are usually valid variable names, so you rarely see the quoted form.

Accessing Members

The primary way of accessing the members of an object is with the square-bracket notation. Given our example above we can access each element as follows:

The square bracket works for all keys in an object. Keys that have not had a value assigned to them evaluate to the special value undefined, which evaluates to false when cast to a boolean.

Hence, you may be tempted to test for the presence of a key like so:

That will behave as expected for they key someVal, but not for the key someOtherVal! To be sure a key really is undefined you have to check its type:

For the sub-set of keys that are valid JavaScript names you can use the shorter dot notation to access object members. The above access examples can be re-written like so:

We were able to use the dot notation a lot of the time, but not all the time. Indirect access always has to be via the square bracket notation, and you can’t use the dot for keys that are not valid variable names like 0, 'key 6' & 'key-7'.

Get a List of Keys

Given an object, you can use the static keys() function from the Object class to get a list of all the keys it contains as an array:

Looping Through Objects

Looping over an object means looping through each of the keys it contains. If you don’t care about the order in which you process the keys you can use a for...in loop directly:

If you need to process the keys in order you need to first extract them form the object with Object.keys() which returns an array, then sort that array with the instance function .sort() which returns another array, and only then can you loop over the keys with a for...of loop.

Explicitly, this is what you need to do:

In the real world we would never write the whole process out so explicitly, we would instead collapse it to simply:

Object Quiz

What will the following code snippets output to the console?

  1. What will the value of midTotal be at the end of this code snippet?

  2. What will the value of z be at the end of this code snippet?

  3. What will the following snippet write to the console?

  4. What will the following snippet write to the console?

  5. What will the value of yokie be after this snippet executes?

  6. What does the following snippet write to the console?

Worked Example — Improving Our Cellular Automata with more Classes

To continue our knowledge consolidation, let’s return to our cellular automata classes. Specifically, I’ll be using my sample solution to the challenge set in instalment 46 as the starting point for our enhancements (tagged release PBS46-Challenge-Solution).

Better States

At the moment the definition of a cell’s state within the code is extremely loose. Basically, any value is fine, and given an instance of bartificer.ca.Automaton, there’s no way to tell what values are and are not considered valid.

When you think about it, for any given CA, the set of possible states is as much a property of that CA as the rules for moving from one state to the next, but as they stand, our prototypes don’t reflect that fact. We should update the API to make that possible.

The first step is to write a simple new class to represent an individual state. It will need just two properties — the state’s underlying value, and a label. The value should be primitive (boolean, number, or string), and the description a string. I’m going to name the class bartificer.ca.State.

However, before we can write the class itself we should to lay some ground-work. We’ll need validation functions for primitive values and non-empty strings, so let’s add those:

We can now make use of these functions when writing our simple bartificer.ca.State class:

That looks like a lot of code, but really, most of it’s JSDoc comments. On closer inspection all we really have is a class with two private instance properties (._value & ._label), a pair of matching read-only accessor instance functions (.value() & .label()), a constructor, and the customary .toString() and .clone() instance functions.

I also added tests for this new class to the test suite, but I won’t clutter this post by copying-and-pasting them here, they’re available via the GitHub repo.

Now that we’ve created this class to represent a state, we need to update the isCellState() function so it only considers instances of bartificer.ca.State to be valid states:

With the exception of a little re-wording in the JSDoc comments, no changes are needed to either the bartificer.ca.Cell or bartificer.ca.Automaton classes.

While no changes were needed to the two main classes themselves, huge changes needed to be made to their QUnit test modules. Each test that used a state had to be updated from using a dummy value like true to using an instance of bartificer.ca.State. Again, I’m not going to clutter this post by pasting in a copy of the test suite, it’s available on GitHub.

Finally, we need to update the sample.html page. Specifically, we need to update the initialisation, step, and render functions so they use each state’s .value() function to get at a state’s underlying value, and return bartificer.ca.State objects as appropriate.

For the most part this was just a case of replacing comparisons to a state to use the state’s .value() accessor, and returning bartificer.ca.State objects instead of raw values, but there is one subtlety I do want to draw your attention to. The neighbourStates array uses null to represent a non-existent neighbour for cells at the edge of the grid. null evaluates to false without error, but null.value() throws an error, hence the need to add a check for null on line 25 above.

I’ve published an updated version of this code, including the updated test suite, on GitHub as the tag release PBS49-Challenge-StartingPoint.

A Challenge

Using the tagged release PBS49-Challenge-StartingPoint on GitHub as a starting point, make the improvements described below.

Part 1 — Add a .equals() Function to bartificer.ca.State

An instance function named .equals() should be added to the bartificer.ca.State class. The function should take one argument, the thing to test. If the thing to test is an instance of bartificer.ca.State, and, it has the same value and label as the instance the function was called on, then it should return true, otherwise, it should return false.

Part 2 — Re-factor the bartificer.ca.Automaton constructor

Note: updated 19 Feb 2018 to correctly reflect the fact that the constructor in the starting point code has 5 required arguments, not three like original stated.

At the moment the constructor in the bartificer.ca.Automaton class takes 6 arguments, five required arguments, and one optional. We’re going to need to add another argument to allow a set of allowed states to be passed, so that would take the constructor to a whopping 7 arguments. Any more than 5 arguments is generally considered confusing and a bad smell, so even before we add another we’ve already got a problem. What can we do?

The first thing we can do is reduce the number of required arguments by implementing a default render function. I suggest using a function that colours states with a truthy value one colour, and a falsy value another.

Since name-value pairs are much less confusing to look at than long lists of anonymous values, it’s common practice to collapse some or all of the arguments into an object that can then be passed as a single argument. A pattern you’ll often see is that required arguments are passed directly, and all optional arguments collapsed into a plain object, often named opts. That’s the approach we’ll take here.

So, the four required arguments should be left as-is, and the remaining two optional arguments collapsed into a single optional object named opts which will expect zero of more of the keys renderFunction, and initialState.

This means that the first line of the constructor needs to change from:

to:

As an example of how the re-factored constructor would be used, the call to the constructor in sample.html will become:

Part 3 — Add a List of Supported States as an Instance Property to bartificer.ca.Automaton

The bartificer.ca.Automaton constructor should be updated to accept an optional argument (via the opts object) named cellStates. If this option is passed, the constructor should check that it is an array of bartificer.ca.State objects, and if not, throw a TypeError. The constructor should also check that no two states in the array contain the same value, and if a clash is found, also throw a TypeError. Assuming the value is valid, each element in the array should be cloned into a new array named this._cellStates. The reason for the cloning is to avoid spooky action at a distance.

If no states are passed this._cellStates should be defaulted to [new bartificer.ca.State(true, 'Alive'), new bartificer.ca.State(false, 'dead')].

Finally, the constructor should build a lookup table to allow states be looked up by their value and save it as this._statesByValue.

A read-only accessor function named .cellStates() should be added. This accessor should return a new array containing references to the values in the internal array (to avoid spooky action at a distance).

A special read-only accessor named .stateFromValue() should be added. This function required one argument, a valid state value, and it should use the ._statesByValue lookup table to get the matching state and return it. If there is no matching state, undefined should be returned.

Finally, an instance function named .hasState() should be added. This function should accept one argument. If the value is passed is an instance of bartificer.ca.State, then true should be returned if a state exists in the ._cellStates array that’s equal to the passed state, otherwise, false should be returned. If the value is not a bartificer.ca.State object, then true should be returned if one of the states in ._cellStates has a value that’s equal to the passed argument, otherwise false should be returned.

Part 4 — Improve .step() in bartificer.ca.Automaton

The .step() function in the bartificer.ca.Automaton class uses the step function to calculate the next state for every cell in the automaton. The function needs to be improved in two ways.

Firstly, if the step function returns something other than an instance of bartificer.ca.State, it should try convert it to a state using the .stateFromValue() function you created earlier. If this fails, a TypeError should be thrown.

Secondly, the next state needs to be checked against the list of allowed states (._cellStates), a TypeError should be thrown.

Final Thoughts

This time we made a start on improving how our Cellular Automaton classes represent cell states, and you’ll be finishing off those improvements as your ‘homework’. Next time we’ll tackle the rendering of states so as to be sure that our prototypes enforce accessibility.