问题
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:
You can see that the functions
save
andvalidate
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:
- Set the value of
this
- Create a new environment (let's call it
env
) for the call - Set up a reference to the function’s
[[Scope]]
property onenv
(this is part of how closures work) - 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) - If the function has a name, add it to
bindings
as a property referring to the function - Add the formal function arguments to
bindings
- Process function declarations, adding their names to
bindings
- Create the
arguments
object, add it tobindings
- Add each variable declared with
var
tobindings
(if not already defined) with the valueundefined
- Process the stepwise code in the function
- 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.
来源:https://stackoverflow.com/questions/26927617/defining-functions-after-return