Defining functions after return

2k views Asked by At

I'm currently reading John Papa's AngularJS style guide and saw the code:

function dataService() {
    var someValue = '';
    var service = {
        save: save,
        someValue: someValue,
        validate: validate
    };
    return service;

    ////////////

    function save() {
        /* */
    };

    function validate() {
        /* */
    };
}

You can see that the functions save and validate are defined after the function returned a value. How does this work? Is it standard-compliant and works in all browsers (say, from IE 6)?

1

There are 1 answers

2
T.J. Crowder On BEST ANSWER

You can see that the functions save and validate are defined after the function returned a value.

That's what it looks like from where they're written, yes, but in fact they're defined before any step-by-step code in the function runs at all. Sometimes this is called "hoisting" the declarations to the top of the function (something similar happens with var, too; more below).

When control enters an execution context (e.g., when you enter a function, enter the global environment at the beginning of the program, or enter eval code), one of the several things that happens before any step-by-step code is executed is that all of the function declarations in that context are processed and those functions are created. Since save and validate are defined by function declarations, they're created before the first step-by-step line of the code runs, and so it doesn't matter that they're after the return.

Here's what the JavaScript engine does when you call a function (e.g., when calling dataService), with the function declarations step highlighted:

  1. Set the value of this
  2. Create a new environment (let's call it env) for the call
  3. Set up a reference to the function’s [[Scope]] property on env (this is part of how closures work)
  4. Create a binding object (let's call it bindings) for the environment to hold our the various names defined by the function (this is another part of how closures work, and also how variable references are resolved)
  5. If the function has a name, add it to bindings as a property referring to the function
  6. Add the formal function arguments to bindings
  7. Process function declarations, adding their names to bindings
  8. Create the arguments object, add it to bindings
  9. Add each variable declared with var to bindings (if not already defined) with the value undefined
  10. Process the stepwise code in the function
  11. Set the call expression’s result

This is laid out in excruciating detail in the spec in §10.4.1 and the sections it links to. (If you go to read that, brace yourself, the prose is...turgid...) That's a link to the current spec, but this was clearly laid out in §10 of the old third edition spec in 1999 as well, and I'm fairly sure it's been true right from the beginning.

Is it standard-compliant and works in all browsers (say, from IE 6)?

Yes. It used to make me nervous, so several years back (probably ~2005) I proved it to myself on all of the then-current and not-quite-dead browsers I could find (including IE6), and it was universally handled correctly. Which isn't actually surprising, because it's what makes this code work:

doSomething();

function doSomething() {
    // ....
}

...and people do that all the time.


This "hoisting" is one of the key differences between function declarations and function expressions. If save and validate were created by function expressions, then it would matter a great deal that they were written after the return — they'd never get created at all:

// It wouldn't work like this, for instance
function dataService() {
    var someValue = '';
    var service = {
        save: save,             // `save` has the value `undefined` at this point
        someValue: someValue,
        validate: validate      // So does `validate`
    };
    return service;

    ////////////

    var save = function() {      // Now this is a function expression
        /* */
    };

    var validate = function() {  // This too
        /* */
    };
}

The save and validate variables would get created (thanks to Step 9 in the above), but as of where they're used, they'd have the value undefined and so the returned object wouldn't be useful.