5

I am learning JavaScript through this website. The link is to the specific chapter that I'm reading right now.

In the book, the author talks about keeping the implementation details of a module on a local scope. He achieves it by doing:

var dayName = function() {
  var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
               "Thursday", "Friday", "Saturday"];
  return function(number) {
    return names[number];
  };
}();

I understand how this works, but I do not understand why he is doing it this way. Why wouldn't he just do...

function dayName(number) {
  var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
               "Thursday", "Friday", "Saturday"];
  return names[number];
}

...which to me looks like a much cleaner and readable code? Given his goal of keeping names within local scope, creating a nested lambda seems redundant.

Is there any real advantage to using the nested function in this case? Or is he using it just for educational purposes?

Thanks!

spicypumpkin
  • 1,209
  • 2
  • 10
  • 21
  • 3
    One may say that it would reduce the garbage collector pressure by not re-allocating the same array multiple times. (but surely it's negligible, unless you invoke this function million times a second) – zerkms Mar 31 '17 at 02:54
  • Possible duplicate of [JavaScript Why return function in a function?](http://stackoverflow.com/questions/35167934/javascript-why-return-function-in-a-function) – Matt Mar 31 '17 at 02:54
  • 1
    @mkaatman The post is asking about how closures work, and I understand it (or at least I think I do). I'm wondering *why* the author is approaching the problem this way, not the alternative (my example). – spicypumpkin Mar 31 '17 at 03:00
  • Read through some of the comments on the duplicate and check out this article specifically: https://davidwalsh.name/javascript-functions – Matt Mar 31 '17 at 03:02
  • 1
    @mkaatman the "duplicate" is about something else. – zerkms Mar 31 '17 at 03:02

5 Answers5

2

The toy example you show is not very compelling. The main advantage of writing it that way is that the array is only created once, rather than every time you call the function. This makes it a little faster, at the expense of keeping the array allocated for the entire session (a common time-space tradeoff). In practice, few people program this way.

The technique becomes more useful when you have multiple functions and they operate on shared data. If one of the functions modifies the variable, this is remembered in the closure, and visible to other functions when they're called later.

var dayName = function() {
  var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
               "Thursday", "Friday", "Saturday"];
  return {
    getDay: function(number) {
        return names[number];
    },
    setDay: function(number, newName) {
        names[number] = newName;
    }
  };
}();

Now you can write:

console.log(dayName.getDay(3)); // prints "Wednesday"
dayObj.setDay(3, "Mercredi");
console.log(dayName.getDay(3)); // prints "Mercredi"

You can't do this with your second form of the function, because it has no memory from one call to the next.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • 1
    Every time you call `dayName(3)` it reinitializes the `names` array, there's no way to change the contents of the array as I did with my `setDay` function. – Barmar Mar 31 '17 at 03:06
  • Oh, right, I did not spot it, sorry. Your initial point in the answer is from the performance perspective, so I missed the point where it switched to the application design. – zerkms Mar 31 '17 at 03:07
  • @4castle Now it is, because I'm returning an object with multiple functions, not a single function. – Barmar Mar 31 '17 at 03:08
  • @4castle Oh, now I see what you mean, I didn't spot that before. – Barmar Mar 31 '17 at 03:09
  • Interesting. Why doesn't the array reinitialize? Shouldn't it be initialized whenever the function `dayName` is called? Or does the initialization only happen when the array is actually "used" in something (i.e. indexing and/or returning)? – spicypumpkin Mar 31 '17 at 03:21
  • 1
    `dayName` isn't a function, it's a variable that contains what the IIFE returns. In my version, it contains an object that contains the two internal functions. – Barmar Mar 31 '17 at 03:24
  • The array is only initialized when the IIFE is called, which is just once when you assign to `dayName`. – Barmar Mar 31 '17 at 03:25
  • @Barmar I missed that too. There are only two characters you have to miss to not realize this is an IIFE; maybe write it differently? – Brian McCutchon Mar 31 '17 at 03:27
1

One case when you might return a function like that is when creating multiple similar event listeners. For example, suppose you have buttons named button1, button2, and button3. You can add click listeners like this (using JQuery for brevity):

button1.click(() => console.log(1));
button2.click(() => console.log(5));
button1.click(() => console.log(42));

This can be written instead as:

function makeLogger(x) {
    return () => console.log(x);
}

button1.click(makeLogger(1));
button2.click(makeLogger(5));
button1.click(makeLogger(42));

This makes an even bigger difference if you can't use the arrow function for compatibility reasons (not using Babel).

Brian McCutchon
  • 8,354
  • 3
  • 33
  • 45
1

In modern JavaScript you'd put code like that in a module (CommonJS, AMD or ES6). In which case something this would be fine

const names = ["Sunday", "Monday", "Tuesday", "Wednesday",
               "Thursday", "Friday", "Saturday"];

function dayName(number) {
  return names[number];
}

module.exports = {
  dayName: dayName,
};

Otherwise yes, it does make some sense to do it his way. Your way you're recreating the names array every time you execute the function. His way it's only created once and it doesn't pollute the namespace (no one else except dayName can see the names array.

You should really look into using modules of one type or another though.

In answer to the question When are nested functions generally appropriate in JavaScript? the general answer is, when you want a closure.

In the case you posted the inner function is a closure. It closes over the names variable.

Closures are a pretty basic and common feature of JavaScript. They are especially useful with callbacks.

gman
  • 100,619
  • 31
  • 269
  • 393
  • The chapter I'm on right now is about modules, so hopefully I will (: By the way, would you consider having `names` in the global scope a (generally) bad practice? The author specifically showed the nested example because he didn't want to pollute the namespace by exposing an implementation detail. – spicypumpkin Mar 31 '17 at 03:35
  • In modern modules they won't pollute the global namespace. The only thing that comes out of a module are references that person using the module assigns to variables in their code. I consider stuff like `names` above inside a module to be module static data. On the other hand if you include the code using a ` – gman Mar 31 '17 at 03:36
0

One possible advantage of this code is caching the result of a highly computational code which has to be done only once (although this is rare) and then using that result for other purposes. Browser related variables are generally cached using this strategy.

rohit anand
  • 286
  • 3
  • 5
0

You said:

I understand how this works, but I do not understand why he is doing it this way

If you do not understand why he is doing it this way then you don't really understand how it really works.

What you see in that example is a closure. And in my opinion this is a better introduction to closures than a lot of the introductory examples out there.

A closure is a bit like a global variable. When using a global variable a function access data outside its own scope. For example:

var x = 2;

function amplify (n) {
    return n * x; // here a function is using x which does not exist
                  // in the function's own scope
}

Closures are similar, except that they are hidden in global scope. Every time an inner function access a variable form an outer function it does so using the same mechanism as how global variables work except that that variable is not visible form global scope. Let's now see a simple example similar to the one in your post:

var amplify;

function init () {
    var x = 2;
    amplify = function (n) { return n * x };
};
init();

console.log(amplify(5)); // prints 10
console.log(x); // syntax error

So in this case, as in the code you posted, the variable x behaves like a global variable but is invisible in global scope. Just like global variables it is visible to all functions declared in the scope it's in. And because x is used by the amplify() function it is not freed or garbage collected when the init() function returns. And that is all what closures are about - this weird contradictory concept of private globals. Nothing more.

In this specific use-case the advantages are minimal. You save memory by not allocating the array each time you call the function but that's about it. But closures are much more powerful than that. You can for example share the same variables with multiple functions yet still hide it from global scope. For example:

var amplify;
var setFactor;
var getFactor;

function init () {
    var x = 2;

    amplify = function (n) { return n * x };
    setFactor = function (n) { x = n };
    getFactor = function () { return x };
};
init();

amplify(5); // returns 10
getFactor(); // returns 2
setFactor(4);
amplify(5); // returns 20
console.log(x); // still a syntax error

As you can see. Closures allow you to capture and share data amongst related functions yet hide that data from all other functions. In a way, closures are almost functional equivalents to objects. So my guide of when to use closures is when you need modularisation but objects are overkill. The most common use-case for this is capturing state for callbacks. But caching (memoization), as shown by the example code you posted, is a good use-case as well.

slebetman
  • 109,858
  • 19
  • 140
  • 171
  • By that statement, I meant that 1. I understand how closures work because if someone told me to write one, I can; 2. I understand he used a closure in order to limit the scope. Therefore my question wasn't about why he used a closure *at all*, but about why he used it *instead of* a simple function. Which is directly answered in the bottom half of your answer. :-) – spicypumpkin Mar 31 '17 at 04:01
  • I was confusing "shared data" with "shared variable name within local scope". Hence me thinking that the data is reinitialized every time I call the nested functions... I was probably confused by the syntax due to the cheaty way I understood IIFEs. – spicypumpkin Mar 31 '17 at 04:07