Test your knowledge with hands-on exercises covering scope chains, closures, and modules. Includes suggested solutions to help you master JavaScript scope concepts.
This appendix aims to give you some challenging and interesting exercises to test and solidify your understanding of the main topics from this book.
It’s a good idea to try out the exercises yourself—in an actual code editor!—instead of skipping straight to the solutions at the end. No cheating!
These exercises don’t have a specific right answer that you have to get exactly. Your approach may differ some (or a lot!) from the solutions presented, and that’s OK.
This exercise asks you to write a program—any program!—that contains nested functions and block scopes, which satisfies these constraints:
If you color all the scopes (including the global scope!) different colors, you need at least six colors. Add a code comment labeling each scope with its color.
BONUS: identify any implied scopes your code may have.
Each scope has at least one identifier.
Contains at least two function scopes and at least two block scopes.
At least one variable from an outer scope must be shadowed by a nested scope variable.
At least one variable reference must resolve to a variable declaration at least two levels higher in the scope chain.
You can just write junk foo/bar/baz-type code for this exercise, but I suggest you try to come up with some sort of non-trivial real’ish code that does something kind of reasonable.
Try the exercise for yourself, then check out the suggested solution at the end of this appendix.
function isPrime(v) { if (v <= 3) { return v > 1; } if (v % 2 == 0 || v % 3 == 0) { return false; } var vSqrt = Math.sqrt(v); for (let i = 5; i <= vSqrt; i += 6) { if (v % i == 0 || v % (i + 2) == 0) { return false; } } return true;}
And here’s a basic implementation of factorize(..):
function factorize(v) { if (!isPrime(v)) { let i = Math.floor(Math.sqrt(v)); while (v % i != 0) { i--; } return [ ...factorize(i), ...factorize(v / i) ]; } return [v];}
If you call isPrime(4327) multiple times in a program, it goes through all its computation steps every time. That’s a lot of wasted work!
Part 1
Use closure to implement a cache to remember the results of isPrime(..), so that the primality of a given number is only ever computed once.
Part 2
Use the same closure cache technique for factorize(..).
Use separate closures for caching of isPrime(..) and factorize(..).
We can see that saving repeated calls improves computation speed. But this usage of closure is making an explicit trade-off: memory.
We’re essentially growing our cache (in memory) unboundedly. If the functions were called many millions of times with mostly unique inputs, we’d be chewing up a lot of memory.
This can definitely be worth the expense, but only if we think it’s likely we see repetition of common inputs.
It might be a good idea to have a more sophisticated caching approach, such as an LRU (least recently used) cache, that limits its size. As it runs up to the limit, an LRU evicts the values that are least recently used.
Try the exercise for yourself, then check out the suggested solution at the end of this appendix.
In this exercise, we’re going to practice closure by defining a toggle(..) utility that gives us a value toggler.You will pass one or more values (as arguments) into toggle(..), and get back a function. That returned function will alternate/rotate between all the passed-in values in order, one at a time, as it’s called repeatedly.
In this third and final exercise on closure, we’re going to implement a basic calculator. The calculator() function will produce an instance of a calculator that maintains its own state:
function calculator() { // ..}var calc = calculator();
Each time calc(..) is called, you’ll pass in a single character that represents a keypress of a calculator button. We’ll restrict our calculator to:
Digits (0-9)
Arithmetic operations (+, -, *, /)
”=” to compute the operation
Operations are processed strictly in the order entered; there’s no ”( )” grouping or operator precedence.For example:
This exercise is to convert the calculator from Closure (PART 3) into a module.We’re not adding any additional functionality, only changing its interface. Instead of calling a single function calc(..), we’ll be calling specific methods on the public API for each “keypress.”This module should be expressed as a classic module factory function called calculator(), so that multiple calculators can be created if desired.The public API should include:
function useCalc(calc,keys) { var keyMappings = { "+": "plus", "-": "minus", "*": "mult", "/": "div", "=": "eq" }; return [...keys].reduce( function showDisplay(display,key){ var fn = keyMappings[key] || "number"; var ret = String( calc[fn](key) ); return ( display + ( (ret != "" && key == "=") ? "=" : "" ) + ret ); }, "" );}useCalc(calc,"4+3="); // 4+3=7useCalc(calc,"+9="); // +9=16
Try the exercise for yourself, then check out the suggested solution at the end of this appendix.
As you work on this exercise, spend some time considering the pros/cons of representing the calculator as a module as opposed to the closure-function approach from the previous exercise.BONUS: Write out a few sentences explaining your thoughts.BONUS #2: Try converting your module to other formats: UMD, CommonJS, and ESM.
Remember, each suggested solution is just one way to approach the problems. They’re not “the right answer,” but they do illustrate a reasonable approach.
var isPrime = (function isPrime(v){ var primes = {}; return function isPrime(v) { if (v in primes) { return primes[v]; } if (v <= 3) { return (primes[v] = v > 1); } if (v % 2 == 0 || v % 3 == 0) { return (primes[v] = false); } let vSqrt = Math.sqrt(v); for (let i = 5; i <= vSqrt; i += 6) { if (v % i == 0 || v % (i + 2) == 0) { return (primes[v] = false); } } return (primes[v] = true); };})();var factorize = (function factorize(v){ var factors = {}; return function findFactors(v) { if (v in factors) { return factors[v]; } if (!isPrime(v)) { let i = Math.floor(Math.sqrt(v)); while (v % i != 0) { i--; } return (factors[v] = [ ...findFactors(i), ...findFactors(v / i) ]); } return (factors[v] = [v]); };})();
The general steps:
Wrap an IIFE to define the scope for the cache variable
In the underlying call, first check the cache
At each return, assign to the cache and return that result
function toggle(...vals) { var unset = {}; var cur = unset; return function next(){ // save previous value back at the end of the list if (cur != unset) { vals.push(cur); } cur = vals.shift(); return cur; };}var hello = toggle("hello");var onOff = toggle("on","off");hello(); // "hello"hello(); // "hello"onOff(); // "on"onOff(); // "off"onOff(); // "on"