Functions and execution contexts in JavaScript
Posted on February 24, 2011Sergio Cinos Senior Architecture Engineer
Functions are the main building block of JavaScript. Functions define the behaviour of things like closures, ‘this’, global variables vs. local variables… Understanding the functions is the first step to truly understand how JavaScript works.
As we already know, functions can access variables declared ‘outside’ the current function’s scope, global variables, and as well as variables declared inside the function and those passed in as arguments. Also, the variable ‘this’ points to ‘the container object’. All of these form an ‘environment’ for our function that defines which variables are accessible by the function and their values. Some parts of this ‘environment’ are defined when the function is defined and others when the function is called.
Understanding what happens internally when a function is called may be a little difficult the first time, mainly due to the technical details and nomenclature. In order to be clear, some parts of this article are simplifications of the technical explanation. The specific details can be found in section 10.1.6 of ECMA 262 (3rd edition).
When a function is called, an ExecutionContext is created. This context defines a big part of the function’s ‘environment’, so let’s see how this is constructed (the order is important):
- The
argumentsproperty is created. This is an array-like object with integer keys, each one referencing a value passed into the function call, in that same order. This object also containslength(number of values passed in the function call) andcallee(reference to the function being called) properties. - Function’s scope is created, using
[[scope]]property and thisExecutionContext. More details on this later. - Variable instantiation takes place now. It has 3 substeps (also in order):
ExecutionContextgets a property for each argument defined in the function signature. If there is a value for that position in theargumentsobject, the value is assigned to the new created property. Otherwise, the property will have the valueundefined.- The function’s body is scanned to detect
FunctionDeclarations. Then, those functions are created and assigned as a property toExecutionContextusing defined names. - The function’s body is scanned to detect variable declarations. Those variables are saved as a property in
ExecutionContextand initialized as undefined.
- The
thisproperty is created. Its value depends on how the function was called:- Regular function (
myFunction(1,2,3)). The value ofthispoints to the global object (i.e.window). - Object method (
myObject.myFunction(1,2,3)). The value ofthispoints to the object containing the function (i.e. the object before the dot). The value ismyObjectin our example. - Callback for
setTimeout()orsetInterval(). The value ofthispoints to the global object (i.e.window). - Callback for
call()orapply(). The value ofthisis the first argument ofcall()/apply(). - As constructor (
new myFunction(1,2,3)). The value ofthisis an empty object withmyFunction.prototypeas prototype.
- Regular function (
Let’s see an example of this process in pseudo-code:
JavaScript code:
function foo (a, b, c) {
function z(){alert(‘Z!’);}
var d = 3;
}
foo(‘foo’,’bar’);
ExecutionContext in the foo() call:
Step 1: arguments is created
ExecutionContext: {
arguments: {
0: ‘foo’, 1: ‘bar’,
length: 2, callee: function() //Points to foo function
}
}
Step 3a: variable instantiation, arguments
ExecutionContext: {
arguments: {
0: ‘foo’, 1: ‘bar’,
length: 2, callee: function() //Points to foo function
},
a: ‘foo’, b: ‘bar’, c: undefined
}
Step 3b: variable instantiation, functions
ExecutionContext: {
arguments: {
0: ‘foo’, 1: ‘bar’,
length: 2, callee: function() //Points to foo function
},
a: ‘foo’, b: ‘bar’, c: undefined,
z: function() //Created z() function
}
Step 3c: variable instantiation, variables
ExecutionContext: {
arguments: {
0: ‘foo’, 1: ‘bar’,
length: 2, callee: function() //Points to foo function
},
a: ‘foo’, b: ‘bar’, c: undefined,
z: function(), //Created z() function,
d: undefined
}
Step 4: set this value
ExecutionContext: {
arguments: {
0: ‘foo’, 1: ‘bar’,
length: 2, callee: function() //Points to foo function
},
a: ‘foo’, b: ‘bar’, c: undefined,
z: function(), //Created z() function,
d: undefined,
this: window
}
After the creation of ExecutionContext, the function starts running its code from the first line until it finds a return or the function ends. Every time this code tries to access a variable, it is read from the ExecutionContext object.
In JavaScript, every single instruction is executed in an ExecutionContext. As we have seen, all code within any function will have an ExecutionContext associated, no matter how the function was created or invoked. Therefore, every single statement inside any function is executed in that function’s ExecutionContext. The code that does not belong to any function but the Global code (code executed inline, loaded via <script>, executed through eval()…) is associated to a special context called GlobalExecutionContext. This context works very much like ExecutionContext, but since we do not have function arguments, only step 3) and 4) take place (this points to global object, usually window). In conclusion, every JavaScript statement runs ‘inside’ an ExecutionContext.
As the execution of our program goes on, it will ‘jump’ from one function to another (via direct calls, DOM events, timers…). As each function has its own ExecutionContext, these function calls will create a stack of contexts. For example, let’s see the following code.
<script>
function a() {
function b() {
var c = {
d: function() {
alert(1);
}
};
c.d();
}
b.call({});
}
a();
</script>
When the JavaScript engine is about to execute the alert() function, the ExecutionContext stack is:
- d() Execution context
- b() Execution context
- a() Execution context
- Global execution context
The most important part of the ExecutionContext stack happens when the function is defined. The key idea to fully understand JavaScript contexts is that every function declaration is executed inside a ExecutionContext (in the previous example, function b() is declared and created inside a() ExeuctionContext). Everytime a function is created, the current ExecutionContext stack is saved in the [[scope]] property of the function itself. All of this happens at function creation. This stack is preserved and tied to the newly created function, even if the original function has finished (this can happen if the original function returns the created function as result, for example).
Now we can explain the step 2) of ExecutionContext creation. At this point, a new ExecutionContext stack is created, pushing the function’s ExecutionContext on top of the mentioned [[scope]] property. This stack is also called ‘scope chain’. Note that the ExecutionContext stack can be (and usually is) different from the calling stack. The later is defined when the functions are called, and the former when they are defined. For example: function a() calls function b() which calls function c(). This would be the calling stack that can be inspected in any debug tool. However, function c() might have been created inside a function d(). The ExecutionContext related to d() is part of the scope chain, but not of the calling stack.
When the code within the function is looking for a variable, the scope chain is examined. The engine starts searching for it in the first ExecutionContext in the chain. As it is the function’s ExecutionContext itself, it corresponds to the functions arguments, declared variables, etc. If it is not found, the engine will then search for the variable in the next ExecutionContext in the stack and so on until it reaches the end of the chain. If it still not found, it returns undefined as the variable value.
And that is all about function internals: just creating ExecutionContexts and stacking them. Let’s sum up the bullet points about functions and execution contexts:
- The value of
thisis not coupled to the function nor is a ‘special’ property, but behaves more like a regular argument. It is defined when the function is called, so the same function can be executed with different values forthis. argumentsis not an array, just a regular object with numbers as property names. So it does not inherit the array methods likepush(),concat(),slice()…- Variables are actually defined in step 3c), no matter where they are defined in the function code. However, initialization takes place when the execution flow reaches the instruction where they are initialized. That is why in our example
dpoints toundefined. It will point to 3 when the execution code reaches the second line of the function code. - You can call a function before it is defined. It is allowed by step 3b) in
ExecutionContextcreation (not true forFunctionExpressions) - All inner functions declarations are created at the
ExecutionContextstep. So an unreachable function declaration will be always created. For example:function foo() { if (false) { function bar() {alert(1);}; } bar(); }It will work (working in IE8, Chrome and Safari5, not in Firefox) because
bar()is created atExecutionContext, before starting function’s code and evaluating theif. - Variables can be hidden. As all the steps take place in order, later steps can overwrite the job done by previous ones. For example, if we define an argument called
fooat function signature, and inside that function we declare another function calledfootoo, the later will ‘hide’ the former when theExecutionContextis finally created. - Closures: a function can access its ‘parent’ function’s variables. When asking for a variable, the value is not found in our current
ExecutionContextbut on the next context in the stack: the context from our ‘parent’ function. You can even build ‘multilevel’ closures that uses the data from its parent, grandparent… functions. - JavaScript has global variables. In this case, the value is found in the last item of the chain, the
GlobalExecutionContext(this is the reason why global variable access is slow; the engine must search for the variable in each context in the stack trace before reaching the global context). Also, you can use semi-global variables: if different functions have a commonExecutionContextin their stacks, any variable declared in that commonExecutionContextwill be available for all those functions like a global variable.
JavaScript core is actually ExecutionContexts and scope chains, as most of the language features raise from the contexts behaviour. If you get used to designing your program as an interaction of contexts, your code will be much simpler and more natural. For example, with contexts in mind, a mixin-based inheritance system is very easy to implement (as opposed to many other languages). Most of the JavaScript loading libraries rely on context management to load modules without polluting the global context. To sum up, it is by thinking in contexts and scope chains (and not in functions and/or objects) that JavaScript unleashes its power. Make sure to understand them as deeply as you can.
Further reading:






February 28, 2011 at 10:41 pm
The fact that variable declarations are already scanned when the code is run explains this behaviour,
foo = ‘foo’; // global
(function() {
foo = 35; // You might think you’re setting a global
var foo;
console.log(foo); // You might expect foo but you get 35
})();
console.log(foo); // ‘foo’
March 3, 2011 at 4:11 pm
Yes, you are right. That is why a lot of authors recommend to put all your ‘var’ declarations in the first line of the function, to avoid that kind of problems.
November 28, 2011 at 8:38 am
Great article. Clear and easy to understand. I tried to read the spec sometime ago, and was only able to grab half of it. With your article, man, I see the complete picture.
I would like to add the following sub-step to step 4.
(If it is not right, please feel to correct it)
If a function is assigned as an event handler, no matter it is a regular function or an object method, ‘this’ refers to the node of event.