Why do variables of inner functions not leak to the outer scope? - javascript

Consider the following snippet of JavaScript:
function outer() {
var x = 10;
function inner() {
var y = 20;
}
}
Obviously, the variable y is not available in the context of outer. Following through the process in the ES5 spec tells us that this is what happens when we enter the outer execution context:
We enter a new execution context (10.4)
Since it's a function, we follow the steps outlined in Entering Function Code (10.4.3)
Let code be the value of F’s [[Code]] internal property.
The [[Code]] internal property of a function is described in Creating Function Objects (13.2). It refers to the FunctionBody as specified by the grammar (the entire contents of the function):
function Identifier ( FormalParameterListopt ) { FunctionBody }
At this point we enter the Declaration Binding Instantiation (10.5) section and do the following:
Create bindings for any arguments in the formal parameter list of the function
For each function declaration in code:
Create a binding in the current scope between function identifier and a Function object (created as specified in Function Definition (13))
There is nothing to say "remove the function we've just processed from code"
Create a binding for the arguments object
For each variable declaration in code:
Create a binding in the current scope for the variable identifier
My question is why, at this point, are bindings not created for variable declarations from inner functions? It seems like code should still contain the entire source text of the outer function (which would include the source text of the inner function). I'm looking for something in the spec which explains the behaviour.
Updated, to be a bit clearer: I am asking about what happens when we enter the context of the outer function. The inner function is never called, and I don't care about what happens when we return from the outer function. I am purely interested in the process, as defined by the spec, of creating bindings for variable declarations when entering a new execution context.

You have a error in your thinking.
Javascript has function-scope. Thus you have a new execution context for each function, thats true. However, after you return from a function this function context expires. That is the reason why you can't access the variables from the inner function after it has returned. You still are in the execution-context of the outer-function but you can't access the execution-context of the inner function anymore.
Citing the spec:
Every return exits an execution context. A thrown exception may also exit one or more execution contexts.
EDIT: To clarify this further: The body of a function declaration is NOT being processed (see 10.5.5.d), only the function identifier and the arguments are passed to the variable environment.

It's right there in the definition of the term code (§10.1):
Function code is source text that is parsed as part of a FunctionBody. The function code of a particular FunctionBody does not include any source text that is parsed as part of a nested FunctionBody.

Related

Are function parameters in JavaScript hoisted?

function foo(a,b){
return a + b;
}
foo(1,2);
Are function parameters hoisted?
Does the variableEnvirnoment at the creation phase of the function execution context looks something like that :
VE = {
{ 0 : undefined , 1: undefined, length: 2 },
{a : undefined, b: undefined},
outer: refToGlobalLE
}
Yes, parameters are hoisted.
When a function is called, each declaration in the function body (var, let, const, and function declarations) is instantiated in the environment record for that execution context. Each formal parameter is also added to the environment record in the same way ([9.2.10 21.c.i and 9.2.10 28.e.i.2]). The full process is described in section 9.2.10 of the spec.
Formal parameters and function declarations are both initialised (note: not "instantiated") during the abstract operation called FunctionDeclarationInstantiation. All other bindings are initialized during evaluation of the function body.
This means that formal parameter bindings are added into the same logical location, in the same way, as those of function body declarations ie. var, let, const, and function declarations (all of which are hoisted, by the way). And it means that formal parameter bindings are initialized in the same way as function declarations (ie. that their corresponding value is set at the top of the function).
Hoisting is a function of the time and place an identifier binding is instantiated. Formal parameter bindings are instantiated (and initialized) in the same way as function declarations. We know function declarations are hoisted, therefore parameter bindings are hoisted. QED.
Prior to ES2015 this hoisting was, as far as I know, invisible, because function parameters are already at the very top of a function. However, parameter default value initializer syntax was added in ES2015, making the hoisting visible in userland.
If formal parameters were not hoisted then the following code would not throw "Uncaught ReferenceError: Cannot access 'x' before initialization" because the default parameter value for z would refer to the outer x:
var x = 'global'
(function(y, z = x, x) {}()) // "Uncaught ReferenceError: Cannot access 'x' before initialization"
Your comment says that 'assuming that parameters are hoisted, x would [be initialized to] "undefined", and as a result the error would not occur as x is already initialized'. Your implication is that hoisting could also be used to explain the absence of an error. This is true, but it does not therefore follow that if an error is observed, hoisting cannot be occurring. Indeed, we can see hoisting is occurring because, as specified in the error message, x in z = x is taken to refer to formal parameter x which is declared later in the program text. The only way this is possible is for hoisting to have occurred.
No, The functions parameters are not hoisted in JavaScript.
function foo(a,b){
return a + b;
}
foo(1,2);
To explain you in brief about the whole process, I would like to explain about how the function itself gets called.
So since you are speaking about the variable environment(which in this case would be the global execution context), the JavaScript thread of execution which basically executes the code line by line would store the function definition code as a value to the foo variable in the memory and it does not executes it yet (it just saves the definition as is). It moves to the next execution line which apparently makes a function call(using parenthesis) to that foo function definition.
Now, when that function gets called foo(1,2), first a new execution context gets created inside the global execution for that foo function. You can imagine this as a box abstracted inside. The arguments 1 and 2 gets mapped with the 'a' and 'b' parameters of the function as variables inside the foo execution context and not the global execution context. Then, the function just returns the value of a+b to the global execution context through the call stack.
Okay, so to check this, if you would have just called the function and then after that defined it like below
foo(1,2);
function foo(a,b){
return a + b;
}
You would probably get an error since the foo function definition is not present in the global execution context and would basically result into a REFERENCE ERROR.
Now, talking about the parameters itself, whether they get hoisted or not. Then No! Those are itself created when the function gets called and a new execution context is created. These variables are block scoped to the function and are not visible to the outer/global execution context. So as and when the function gets returned to the callee, the variable inside of it are just garbage collected.
Hope that answers your question.

Does the JavaScript specification define how the runtime should keep track of objects created in expressions?

When function foo is invoked, the [[Call]] method is called and an execution context is created. IIUC, as part of the creation of the execution context, an environment record for str is created and added to the lexical environment within the execution context.
So I "know" how JavaScript logically keeps track of str.
When control moves to the line with the anonymous function expression, a function object is created, and then executed. Does the specification define a position in the execution context to store a reference to such a function object, or, does it not, because the function object is part of an expression and a reference to it is simply thrown away, unless it is assigned to something with a name?
In this instance, there is presumably a logical period when the anonymous function object exists, but has not yet been invoked as part of the IIFE, so a reference to the function object might be expected to exist for this short period, or is the IIFE indivisible?
function foo() {
const str = 'foo';
(function() { })();
}
foo()
Does the specification define a position in the execution context to store a reference to such a function object
No.
the function object is part of an expression and a reference to it is simply thrown away, unless it is assigned to something with a name?
Precisely that.
there is presumably a logical period when the anonymous function object exists, but has not yet been invoked as part of the IIFE, so a reference to the function object might be expected to exist for this short period, or is the IIFE indivisible?
Well, the reference to the function exists inside the expression interpreter of course, somewhere in RAM the engine keeps a pointer to the object. But no, this has nothing to do with the scope that the expression is evaluated in, it does not create a variable binding for it.
From a JS perspective, the expression is indivisible, you cannot inspect its state anyhow without changing the expression.

Hoisting in ES6 with respect to `let` and block scope

When I run the following code written using ES6 let:
function doSmth(){
age=27;
}
let age;
doSmth();
console.log(age);
I get the output correctly as 27.
Now I am a bit confused as to how this works. Specifically, I have read is that let has a block scope.
So, how is it that let age, which is declared outside the function doSmth, is the same variable inside the function doSmth?
Is that something to do with global scope, but if yes, then what role does block scope have exactly ?
"Let has block scope" doesn't mean that the variable must be defined in the same block - it means that the level at which the variable is defined can be a block.
In contrast, the level at which a var variable is defined cannot be a block, unless that block is also a function block. If a var variable is initialized inside a non-function block, it will be hoisted out to the nearest outer block which is also a function block.
No matter whether a variable is declared with var, let, or const, it will be referencable anywhere else inside the same block in which it was declared, including inside child blocks. (For let and const, the line that declares the variable, eg const foo =, must also have run for a reference to foo to work without throwing.)
Another way of looking at it is: if a variable is referenced somewhere, the interpreter looks for whether it's initialized in the current block. If so, that's the binding that's used. Otherwise, it looks in the next outer block to see if the variable is initialized there - if so, that's the binding that's used. And so on. If it gets all the way to the top level, and no binding has been found, and it's not a property of the global object, that reference will throw a ReferenceError.
In your code, the age variable happens to be global, since it's defined on the top level, but that doesn't really matter - the code would work the same if age was just an an outer block, but not on the top level, eg:
function foo() {
function doSmth() {
age = 27;
}
let age;
doSmth();
console.log(age);
}
foo();
Now I am a bit confused as to how this works. Specifically, I have read is that let has a block scope.
Yes, let has block scope. But a block such as inside your doSmmth() function has access to ALL the variables in parent scopes that have been defined or hoisted at the time of the function execution.
So, let age; is in the parent scope and has been defined by the time doSmth() executes. Therefore the interpreter tasked with looking up a variable named age finds it in the parent scope just fine.
So, how is it that let age, which is declared outside the function doSmth, is the same variable inside the function doSmth?
Here's how the interpreter works in this regard. Your code executes in these steps:
Parse the code you've presented.
The function doSmth() definition is encountered. Functions are hoisted to the top of the containing function scope so that definition is immediately added to this scope object. let age is also encountered in the parsing and is added to the parsed scope, but it is marked in a way that it will not yet be accessible to code.
Start executing the code. A new scope object is created from the parsed code.
let age; is encountered. This marks the age variable which was previous put into this scope at parsing time as now available for code to use.
Call doSmth(). This pushes a return address on the callstack and as it sets up to execute doSmth(), it creates a new function scope.
It then executes age = 27 inside that function. The interpreter looks in the local scope for a symbol named age. It doesn't find one. So, it goes up the scope chain and looks in the parent scope. It finds a matching symbol and uses that one.
The function returns which pops a return address off the call stack and resets the current scope back to the parent scope and the execution goes to right after where doSmth() was and executes the console.log(age).
The interpreter looks in the local scope for a symbol named age and finds it and uses it.
So, the main keys here that it sounds like you may be confused by are:
If a symbol isn't found in the local scope, then the parent scopes are searched for the symbol name.
let does create a block-scoped symbol, but it can still be accessed by any child functions also declared in that scope.
In this particular code, there wouldn't be any difference between let and var and it isn't hoisting that makes this work. It's parent scopes.
In your code, both doSmth and age are in global scope. Windows for browser and module object for node. So they basically are in the same scope.
Your code will go through two phases.
Compilation where all hoisting will occur (var declared variables).
Execution where actual execution of code will occur.
In first phase you will declare function doSmith and age variable. Before executeion phase JS engine knows what is age variable and what is doSmith.
In next phase i.e. execution phase, execution will occur,
doSmth();
console.log(age);
will get executed. Hence in age variable is accessible to function doSmith in execution phase.

Javascript Function Expressions

I have recently been trying to learn javascript and have a couple of questions.
When you create a function expression:
var greet = function(){
console.log('Hi');
}
Is that creating a function object and having the variable "greet" point to that function object in memory?
My second question if you have a function like this:
function log(a){
console.log(a);
}
Then you make a call to that function:
log(greet); //greet is the function expression declared above.
So I know that when a function object is created there are two properties that are given to the object. The name (if provided, otherwise anonymous) and a code property which stores the code contained inside the parentheses of the function. Now I am a little confused on where the parameter "a" in the log function gets attached to in a function object. Is "a" just another property of the function object log and it simply just points to the memory address of anything that is passed into the log function? Where in this case it was a function expression called greet. Any input would be appreciated. Thank you!
When a function is called as in:
log(greet)
then the identifier log is resolved in the current execution context and, if not found, is searched along the scope chain, ending at the global execution context. If not found, an error is thrown.
In this case, log has been defined using a function declaration so it exists in the global scope so is found. Its value is checked to make sure it's callable (again, if not, an error is thrown) if it is, it's called.
In the call, the identifier greet is resolved and its value passed to the function. If greet can't be resolved (i.e. it doesn't exist on the scope chain), an error is thrown. In this case, it resolves to a reference to the function assigned to greet.
When log is executed, a new execution context is created. The function declaration for log defines a formal parameter a (in its formal parameter list), so a is created as a local variable for log. The values in the call are passed to identifiers in the formal parameter list in order, so the value of greet is assigned to a. Note that initialisation and creation of a new execution context occurs every time a function is called.
The same process is followed when calling:
console.log(a);
so that within console.log, the reference to greet is passed as the first parameter, so it now references the greet function.
The behaviour of console.log is entirely implementation dependent so the internals are unknown, but for functions most tend to just call the function's toString method.
It's a handy feature of ECMAScript that an arguments object is created of the arguments passed to functions, so the values passed are always available as numeric properties of the arguments object if there is no parameter for them to be assigned to. So console.log doesn't have to define any formal parameters, it can just loop over its arguments object and process the passed values in turn.

Details about what happens when entering a function declared in the global scope is missing from ECMAScript Specification v5?

The ECMAScript specification goes into detail about what happens when control enters the execution context of a function within a function.
function foo() {
function bar() {
}
bar(); // Control will be given to the bar function. Details specified by spec
}
There also is an explanation of what happens when control enters global code.
<script>
// Entering global code! Details specified by spec
</script>
However, there is nothing specifying what happens when entering control for a function defined in the global code.
<script>
function foo() {
}
foo(); // Calling a function defined in the global environment...not specified by spec
</script>
Edit: The reason this is important to me is because I'm curious what the internal [[Scope]] property of the function called by the global code will be. I assume it will be the lexical environment of the global execution context, but there's nothing that specifies this in the specification.
I think you misinterpreted that sentence (from §10.4.3, Entering Function Code):
The following steps are performed when control enters the execution context for function code contained in function object F, a caller provided thisArg, and a caller provided argumentsList […]
It does not mean that the function which is entered must be contained in F, but that the code that is entered is contained in the function F (which you are invoking).
The [[Call]] method which is used when calling a function does not distinguish between global/local declared or invoked functions.
Functions declaraed in global code are instantiated during Declaration Binding Instantiation of the global code in step 2 of 10.4.1.
This is done via step 5.c of 10.5 which actually creates each such function object via the first algorithm in section 13. Note that this sets the [[Scope]] of the function to the VariableEnvironment of the current execution context. The current execution context was set by step 1 of 10.4.1 (via 10.4.1.1) to the Global Environment.

Categories