JavaScript Execution – II

To read the part 1 of this post, click here.

Simple function expression

Let’s take this example:

// Example 1
1. var foo;
2. foo = function () {
3.    var a = 10;
4.    console.log (10);
5. }
6. foo ();
7. console.log ('Done');

I have created foo variable before assigning a value to keep the execution simple. Let’s go through the two phase execution for this piece of code.

Phase 1:

  • Line 1: New variable foo, allocate memory for foo.
  • Line 2: Skip [assignment].
  • Line 3-5: Skip [function].
  • Line 6. Skip [execution].
  • Line 7: Skip [execution].

Phase 2:

  • Line 1: Skip
  • Line 2: Assignment, assign anonymous function to foo, since function in JavaScript is also treated as values, it is possible to assign the function to a variable. But this anonymous function keeps a reference to the content of the function which is stored in the heap [same as function statement]. And foo now points to that reference location in the heap.
  • Line 3-5: Skip
  • Line 6: Execution, invoke function foo. What’s function foo here? We have not created any named function foo, but foo indeed has a reference to the anonymous function [line 2]. Execute it in the same way, as we did earlier.
  • Line 7: Execution, invoke function console.log.

Functions with return value

// Example 2
1. function foo () {
2.   return 'test';
3. }
4. var str = foo ();
5. console.log (str);

The function here returns a string test and is assigned to a variable str. Let’s see the execution:

Phase 1:

  • Line 1: New variable foo of type function, create a reference [as done earlier].
  • Line 2-3: Skip [function].
  • Line 4: New variable str, allocate memory for str.
  • Line 5: Skip [execution].

Phase 2:

  • Line 1-3: Skip [function].
  • Line 4: Assignment
    • Assign the value foo () to str, but what’s foo ()?
    • Execution, invoke function foo.
    • As discussed earlier, after the execution of a function, it needs to return to it’s previous location. It does so when it encounters the return statement. And this is the reason every function returns a value [if not mentioned, it returns undefined].
    • foo in this case returns a value, which is test.
    • Line 4 says, assign the value of foo() to str and invocation of foo returns test, hence test is assigned to str and the execution pointer resumes to line 5.
  • Line 5: Execution, invoke function console.log

An important point to note here is, each function is executed in its own scope and all the functions run for completion [not talking about generators of ES6 yet]. Which means, once the execution enters a function, it will resume its old line of code only when that function completes its execution. This completion is understood by the return value of the function.

How does the so called execution pointer knows where to return once it encounters the return statement inside a function?

During the execution phase [Phase 2], whenever it encounters a function invocation, while leaving the current line of execution [which technically is called context or scope], it creates a back-reference to the current context.

So, let’s say

  • During the execution of foo, the execution pointer encounters another function bar.
  • It leaves the foo context, creates a back-reference from the context of bar to the context of foo, something like this: foo <-- bar.
  • When bar returns, it know where to go, i.e. back to foo.

Hoisting in function

If you were paying attention, you should know by now that function expressions cannot be hoisted while function statement can be.

The phase 2 of the JavaScript engine requires to execute or invoke the function. The execution requires a reference to the content of the function stored somewhere in the heap. But in case of function expression, it won’t find any.

Let’s see an example of a function statement which gets hoisted, followed by another of a function expression, which does not get hoisted.

// Example 3
1. foo ();
2. function foo () {
3.   console.log ("Hoisted!");
4. }

Phase 1:

  • Line 1: Skip [execution].
  • Line 2: New variable foo of type function, create a reference to the content of the function.
  • Line 3-4: Skip [function].

Phase 2:

  • Line 1: Execution, invoke function foo.
  • Rest of the lines, like other examples.
// Example 4
1. bar ();
2. var bar = function () {
3.   console.log ("Hoisted?");
4. }

Phase 1:

  • Line 1: Skip [execution].
  • Line 2: Here is the difference.
    • New variable bar not of type function, but it is like any other variable.
    • Allocate memory for bar.
  • Line 3-4: Skip [function].

Phase 2:

  • Line 1: Execution, invoke function bar

Would it be possible? We have seen earlier that if memory is allocated to a variable and if we try to access it, it gives the value undefined.

Read more about undefined here.

Line 1 in this case is asking to execute/invoke undefined, which is not a function. An execution requires a reference to the content of the function. Therefore, this code will result into error.

Uncaught TypeError: bar is not a function