102
function foo(a) {
    if (/* Some condition */) {
        // perform task 1
        // perform task 3
    }
    else {
        // perform task 2
        // perform task 3
    }
}

I have a function whose structure is similar to the above. I want to abstract task 3 into a function, bar(), but I wish to limit the access of this function to only within the scope of foo(a).

To achieve what I want, is it right to change to the following?

function foo(a) {
    function bar() {
        // Perform task 3
    }

    if (/* Some condition */) {
        // Perform task 1
        bar();
    }
    else {
        // Perform task 2
        bar();
    }
}

If the above is correct, does bar() get redefined every time foo(a) gets called? (I am worrying about waste of CPU resource here.)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
tamakisquare
  • 16,659
  • 26
  • 88
  • 129

5 Answers5

145

Yes, what you have there is right. Some notes:

  • bar is created on every call to the function foo, but:
    • On modern browsers this is a very fast process. (Some engines may well only compile the code for it once, and then reuse that code with a different context each time; Google's V8 engine [in Chrome and elsewhere] does that in most cases.)
    • And depending on what bar does, some engines may determine that they can "inline" it, eliminating the function call entirely. V8 does this, and I'm sure it's not the only engine that does. Naturally they can only do this if it doesn't change the behavior of the code.
  • The performance impact, if any, of having bar created every time will vary widely between JavaScript engines. If bar is trivial, it will vary from undetectable to fairly small. If you're not calling foo thousands of times in a row (for instance, from a mousemove handler), I wouldn't worry about it. Even if you are, I'd only worry about it if I saw a problem on slower engines. Here's a test case involving DOM operations, which suggests that there is an impact, but a trivial one (probably washed out by the DOM stuff). Here's a test case doing pure computation which shows a much higher impact, but frankly even, we're talking a difference of microseconds because even a 92% increase on something that takes microseconds to happen is still very, very fast. Until/unless you saw a real-world impact, it's not something to worry about.
  • bar will only be accessible from within the function, and it has access to all variables and arguments for that call to the function. This makes this a very handy pattern.
  • Note that because you've used a function declaration, it doesn't matter where you put the declaration (top, bottom, or middle — as long as it's at the top level of the function, not inside a flow control statement, which is a syntax error), it gets defined before the first line of step-wise code is run.
Eugen Konkov
  • 22,193
  • 17
  • 108
  • 158
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thx for your answer. So are you saying that this is a negligible performance hit? (given that a copy of `bar` is created on every call of `foo`) – tamakisquare Apr 18 '12 at 07:06
  • 2
    @ahmoo: With JavaScript performance, the answer is almost always: It depends. :-) It depends on what engine will be running it and how frequently you'll be calling `foo`. If you're not calling `foo` thousands of times in a row (for instance, not in a `mousemove` handler), then I wouldn't be worried about it at all. And note that some engines (V8, for instance) will inline the code anyway, completely eliminating the function call, provided doing so doesn't change what's happening in a way that can be detected externally. – T.J. Crowder Apr 18 '12 at 07:30
  • @T.J.Crowder: can you comment on robrich's answer? does that solution prevent the recreation of `bar()` on each call? also, would using `foo.prototype.bar` to define the function help any? – rkw Apr 18 '12 at 07:41
  • 4
    @rkw: Creating the function once, like robrich's answer does, is a useful way to avoid the cost of creating it on every call. You lose the fact that `bar` has access to the variables and arguments for the call to `foo` (anything you want it to operate on, you have to pass it), which can complicate things a bit, but in a performance-critical situation where you've seen an actual problem, you might refactor like that to see if it solves the problem. No, using `foo.prototype` wouldn't really help (for one thing, `bar` would no longer be private). – T.J. Crowder Apr 18 '12 at 07:53
  • @ahmoo: Added a test case. Interestingly, I get different result from Guffa's test case, I think his function may be just a bit *too* simple. But I still don't think performance need be an issue. – T.J. Crowder Apr 18 '12 at 07:59
  • @ahmoo: Added a better test case. – T.J. Crowder Apr 18 '12 at 08:22
  • @T.J.Crowder - you have wowed me with your deep and comprehensive analysis of my question. Much appreciated. Normally, I don't change accepted answer from one to another, but that's the only way to show my appreciation. Cheers. – tamakisquare Apr 20 '12 at 20:29
  • Awesome answer mate... really useful and intelligent. Good to see people on here are still interested in explaining their answers rather than just providing one that works. – doz87 Sep 08 '15 at 06:40
  • Declaring all nested functions at the bottom is best for readability. – doubleOrt Jun 27 '18 at 13:08
  • @doubleOrt *In your opinion.* And as a matter of style, off-topic. – T.J. Crowder Jun 27 '18 at 13:31
  • @T.J.Crowder How is this not objective ? When you read the implementation of `foo`, you just want to get a picture of how it does what it does. If something has been abstracted into a function within that function, it means the author thought it should be abstracted away, i.e he wanted you not to see it. If you declare those functions at the top, I will have to scroll for a while to find the place where the actual code starts, this is not a problem if it is declared at the end. – doubleOrt Jun 27 '18 at 13:38
  • @doubleOrt - What if the details of those functions are important for understanding `foo`? So, arguments and counter-arguments; subjective. :-) – T.J. Crowder Jun 27 '18 at 13:40
  • @T.J.Crowder Then you go and see their implementation :) but in most cases, a good name should work. What do you think ? – doubleOrt Jun 27 '18 at 13:42
  • @doubleOrt - I think it's subjective, and that I'm not particularly interested in a conversation about it. – T.J. Crowder Jun 27 '18 at 13:42
  • @T.J.Crowder I understand. Cheers then :) – doubleOrt Jun 27 '18 at 13:44
16

This is what closures are for.

var foo = (function () {
  function bar() {
    // perform task 3
  };

  function innerfoo (a) { 
    if (/* some cond */ ) {
      // perform task 1
      bar();
    }
    else {
      // perform task 2
      bar();
    }
  }
  return innerfoo;
})();

Innerfoo (a closure) holds a reference to bar and only a reference to innerfoo is returned from an anonymous function which is called only once to create the closure.

Bar is not accessible from the outside this way.

Michiel Borkent
  • 34,228
  • 15
  • 86
  • 149
  • 1
    Interesting. I have limited exposure to javascript, so closure is something new to me. You have marked a starting point for me to study closure, however. Thanks. – tamakisquare Apr 18 '12 at 07:39
  • How often do you use closure to deal with variable/function scopes ? E.g if you have 2 functions that need to access the same 3 variables, would you declare the 3 variables in a closure along with the 2 functions and then return the 2 functions ? – doubleOrt Jun 27 '18 at 13:11
10
var foo = (function () {
    var bar = function () {
        // perform task 3
    }
    return function (a) {

        if (/*some condition*/) {
            // perform task 1
            bar();
        }
        else {
            // perform task 2
            bar();
        }
    };
}());

The closure keeps the scope of bar() contained, returning the new function from the self-executing anonymous function sets more visible scope to foo(). The anonymous self-executing function is run exactly once, so there is only one bar() instance, and every execution of foo() will use it.

robrich
  • 13,017
  • 7
  • 36
  • 63
  • Interesting. I gotta look up closure then. Thanks. – tamakisquare Apr 18 '12 at 07:39
  • How often do you use closure to deal with variable/function scopes ? E.g if you have 2 functions that need to access the same 3 variables, would you declare the 3 variables in a closure along with the 2 functions and then return the 2 functions ? – doubleOrt Jun 27 '18 at 13:14
  • How often do I use closures? All the time. If I had 3 variables needed by 2 functions: 1. (best) pass the 3 variables into both functions -- the functions can be defined once and only once. 2. (good) create the 2 functions where the variables are outside the scope of both. This is a closure. (Basically the answer here.) Sadly, the functions are redefined for each new use of the variables. 3. (bad) don't use functions, just have one big long method doing both things. – robrich Jun 28 '18 at 16:37
  • You said *the functions are redefined for each new use of the variables.* Can you explain this please? @robrich – Budi Salah Dec 25 '21 at 19:42
  • @BudiSalah as you execute the `foo` function, it creates the bar function and the anonymous function it returns. Call it a second time, and you get two new functions created. Call it a third, and the functions are newly created again. – robrich Dec 29 '21 at 04:54
8

Yes, that works fine.

The inner function is not recreated each time you enter the outer function, but it's re-assigned.

If you test this code:

function test() {

    function demo() { alert('1'); }

    demo();
    demo = function() { alert('2'); };
    demo();

}

test();
test();

it will show 1, 2, 1, 2, not 1, 2, 2, 2.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • Thanks for your answer. Should the reassignment of `demo()` each time `test()` is called be a performance concern? Does it depend on the complexity of `demo()`? – tamakisquare Apr 18 '12 at 07:14
  • 1
    I made a performance test: http://jsperf.com/inner-function-vs-global-function The conclusion is that generally it's not a performance issue (as any code that you put in the functions will take a lot longer to run than creating the function itself), but if you would need that extra performance edge, you would have to write different code for different browsers. – Guffa Apr 18 '12 at 07:28
  • Thx for spending the time to create the test and, as well as, sharing your points on performance. Much appreciated. – tamakisquare Apr 18 '12 at 07:35
  • You've said "the inner function is not recreated each time" with great confidence. According to the **spec**, it is; whether an engine optimizes it depends on the engine. (I expect most will.) I'm intrigued to see that your test case and mine have such varying results: http://jsperf.com/cost-of-creating-inner-function Not that I think performance is an issue, though. – T.J. Crowder Apr 18 '12 at 07:58
  • @T.J.Crowder: Well, yes, it's an implementation detail, but as modern Javascript engines compile the code, it won't recompile the function each time that it's assigned. The reason that the result of the performance tests differ, is because they test different things. My test compares global functions with local functions, while your test compares a local function with inlined code. Inlining the code will of course be faster than calling the function, it's a common optimisation technique. – Guffa Apr 18 '12 at 08:04
  • @Guffa: And again there's that certainty. ;-) I'm not at all sure you can say for certain the code is inlined in the one case and not in the other. But my test case is intentionally a close parallel to the code in the OP's question. – T.J. Crowder Apr 18 '12 at 08:07
  • @Guffa - FYI. T.J. Crowder has expanded his answer to cover more details and thus I have changed to accept his answer instead. Still, your answer is good. Wish I could accept multiple answers. – tamakisquare Apr 20 '12 at 20:32
  • Why the downvote? If you don't explain what it is that you think is wrong, it can't improve the answer. – Guffa Mar 07 '16 at 13:00
0

I created a jsperf to test nested vs. non-nested and function expressions vs. function declarations, and I was surprised to see that the nested test cases performed 20x faster than non-nested. (I anticipated either the opposite or negligible differences).

https://jsperf.com/nested-functions-vs-not-nested-2/1

This is on Chrome 76, macOS.

Michael Liquori
  • 619
  • 10
  • 16