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

In the previous instalment we started our exploration of the new features ES6 brought to JavaScript with a look at block scoped variables. We learned that var will continue to work as it always has, defining function-scoped variables, but that we can now use let and const to define block-spoped variables and constants.

We’ll continue our exploration of ES6 today by looking at how function arguments have been improved, and learning about a new type of loop designed to make looping over object properties easier.

There is no ZIP file for this instalment, instead, I’ve published my solution to the challenge from the previous instalment (which is also the starting point for the next challenge) as a tagged release on GitHub. You can download it using the big green button labeled Clone or Download.

PBS 43 Challenge Solution

The challenge set at the end of the previous instalment was very simple — update the bartificer.ca prototypes to use ES6’s let and const keywords as appropriate, using the test suite to ensure you don’t introduce any bugs in the process. This kind of internal change without a change in functionality is known in software engineering jargon as code refactoring. Any bugs you add when doing this kind of work would be know as regressions. The fact that we have a test suite makes regressions much easier to find and fix before release.

I’ve published my sample solution to GitHub as the tagged release PBS43-Challenge-Solution of bartificer.ca.js.

For the most part this was simply a matter of replacing the var keyword with the let keyword, but there were a few subtitles that I want to draw your attention to.

Firstly, safely declaring shared global namespaces like bartificer still needs to be done with var. Why? Because the same namespace is used as the parent namespace for many separate APIs, and it needs to be possible to use multiple such APIs within a single page.

In other words, this line needs to remain as it is:

You can try re-write it with let or const, but you’ll run into a brick wall

If you were to try do the following, what would happen?

You’ll get an error. Why? Because, as we learned last time, let declarations don’t get hoisted.

Remember, the assignment operator (=) has the lowest precedence (we learned about operator precedence way back in instalment 12), so it happens after the ternary operator. That means the ternary operator tries to access the bartificer variable before it’s been declared. The reason this weird line of code works with var is that var declarations do get hoisted.

Even if let variables were hoisted, there would be an even bigger problem with using let to conditionally initialise a shared global namespace that may already be initialised like bartificerlet throws an error if you try to use it to re-declare an already declared variable!

The second subtly I want to draw your attention to is that there were opportunities to reduce the scope of some variables, which is generally better. It’s a good rule of thumb that you want the scope of your variables to be as small as needed, but no smaller.

As an example, let’s look at bartificer.ca.Automaton.prototype.step():

Because var is function-scoped, the two sets of for loops share the same x and y variables. That’s not something we want, we just didn’t have a choice in the matter with var.

We could just replace var with let, and leave the scope as-is, but while that would result in working code, it wouldn’t be in keeping with the spirit of ES6, or our aim of minimising variable scope. So, instead, we should create separate instances of x and y for each set of loops:

For clarity, I’ve highlighted the scopes of the two separate x variables in the snippet above.

ES6 — Improved Function Arguments

ES6 improves function argument handling in two important ways. Firstly, it allows default values to be specified for optional arguments, and secondly, it provides a nice new mechanism for capturing arbitrarily many arguments.

Default Argument Values

It’s quite common to have functions with optional arguments. When the function is called without an optional argument your code needs to provide a default value to use instead. In previous versions of JavaScript you had to do this defaulting within the body of the function, so default values were not easy to see at a glance.

Let’s use a trivially simple example to illustrate the point — a function to increment a value. The first argument must be the value to increment, and the second optional argument is the amount to increment by, which defaults to one:

A seasoned programmer might shorten that function to:

However, regardless of which of those implementations you choose, the fact that i defaults to 1 is not immediately obvious at a glance — you have to work through the logic of the function to figure that out. This is a contrived overly simple example with just one optional argument, in reality the code for defaulting arguments will be mixed in with many more lines of code, so the default values will be even less obvious.

With ES6 we can give default values right within the function declaration, so our function now becomes just:

I think you’ll agree that’s much clearer!

Variadic AKA Rest Arguments

Way back in instalment 16 we learned how to write functions that can process arbitrarily many arguments by looping over the special arguments object that exists within each function. We illustrated the point with this sample function which multiplies together arbitrarily many numbers:

You can see the function in action with calls like these:

The above code works, but it’s not at all clear from the function declaration what arguments the function expects — you have to read the code to figure that out.

ES6 adds a feature some other languages have had for many years, so-called variadic or rest arguments. A function can only define a single variadic argument, and it has to be the last argument. Why? Because a variadic argument collects all the remaining arguments together into a single array. You can think of a variadic argument as ‘all the rest of the arguments’, hence the nickname rest arguments.

In ES6 you define an argument as being variadic by pre-fixing the name with three periods. So, we could re-write the above example like so:

This has two obvious advantages. Firstly, the fact that this function accepts arbitrarily many arguments is now obvious from the function declaration, and secondly, because n is now a true array, we can use functions from the Array prototype on it (like .forEach()).

In the above example the variadic argument is the only argument, but that doesn’t have to be the case, the variadic argument just has to be last.

For example, the following function takes an operator as the first argument, and then applies that operator to all the other arguments passed. So, it has one regular argument, and then all other arguments passed get collapsed into the variadic argument:

Note that you can’t assign a default value to a variadic argument.

ES6 — Looping Over Objects with for ... in Loops

Way back in instalment 17 we learned how to loop over objects with the help of the Object.keys() function. We used the following example to illustrate the point:

Note that this example is designed to be run inside the PBS JavaScript playground, hence the calls to pbs.say().

With ES6 there’s an easier way — the so-called for ... in loop:

The example above uses a plain object (tlaLib), but things get a little more complicated when looping over prototyped objects. Why? Because prototyped objects can contain both instance properties and static properties.

Revision — Instance -v- Static Properties

Prototyped objects can have two distinct kinds of property — those that belong to the instance itself, and those that belong to the prototype.

Each instance of a prototype has its own separate copy of each instance property, hence the name.

Properties that belong to the prototype itself are different. There’s just a single copy of those properties that all instances share. We’ve been referring to these as static properties, but you may also see them referred to as prototype properties, or even class properties).

The following simple prototype contains one of each kind of property:

We can create two instances of this prototype with the following:

Each of these instances has their own copy of the instance property colour, as demonstrated by the following code snippet:

However, both instances share a reference to the single static property aka, as illustrated by the following:

When iterating over an object’s properties, for ... in will iterate over both the instance and static properties. You may or may not want that behaviour!

If you only want to iterate over an object’s instance properties, sometimes referred to as an object’s own properties, you need to make use of the .hasOwnProperty() function provided by the built-in Object prototype.

This function takes a string as an argument and returns true if the object has an instance property with that name, and false otherwise.

You can see this in action with the following code snippet:

There’s one final complication with for ... in loops — they only iterate over so-called enumerable properties. In practice what that means is that for ... in loops ignore standard properties like length provided by the built-in prototypes like Object and Array.

To illustrate this point, an array containing one element has two instance properties, 0, and length, but only the 0 property is enumerable:

A Challenge

Using my solution from the previous instalment as your starting point, update the test suite (test/tests.js) to use the ES6 features we’ve learned so far.

Final Thoughts

Having learned about let and const, default argument values, variadic arguments, and for ... in loops, we’re not even half way through all the cool feature ES6 added to JavaScript. We’ll continue our exploration of ES6 next week with a look at how arrays and strings have been improved.