Lesson 37-In-Depth Analysis of JavaScript Functions and Variables

Underlying Principles of JavaScript Variable System

Variable Storage Mechanism

JavaScript variables are managed at the low level through variable environments and lexical environments, which form the foundation of JavaScript’s scope system. Internally, variables are stored in specific data structures, with the exact implementation varying depending on the JavaScript engine (e.g., V8, SpiderMonkey).

Basic Structure of Variable Storage:

  • Global Variables: Stored as properties of the global object (window in browsers, global in Node.js).
  • Local Variables: Stored in the function call stack’s frame.
  • Closure Variables: Stored in a specialized closure data structure.
// Global variable example
var globalVar = 'I am global'; // Stored in the global object

function test() {
  // Local variable example
  var localVar = 'I am local'; // Stored in the function call stack's frame

  if (true) {
    // Block-scoped variable (ES6 let/const)
    let blockVar = 'I am block scoped'; // Stored in the lexical environment
  }
}

Variable Declaration and Hoisting

JavaScript variable declarations are “hoisted” to the top of their scope during the compilation phase, but their initialization remains in place. This behavior is handled by the JavaScript engine before code execution.

Hoisting with var Declarations:

console.log(a); // undefined (no error, variable is declared but not initialized)
var a = 10;

Internally equivalent to:

var a; // Declaration hoisted
console.log(a); // undefined
a = 10; // Initialization stays in place

Hoisting with let/const Declarations (Temporal Dead Zone):

console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 20;

Although let and const are hoisted, they reside in a Temporal Dead Zone (TDZ), causing a ReferenceError if accessed before their declaration.

Variable Types and Memory Allocation

Memory allocation for JavaScript variables depends on their type:

  1. Primitive Types:
    • Stored in stack memory.
    • Includes: string, number, boolean, null, undefined, symbol, bigint.
    • Stores the value directly.
  2. Reference Types:
    • Stored in heap memory.
    • Includes: object, array, function, date, etc.
    • Stack stores a reference address pointing to the heap.
// Primitive type example
let num = 42; // Stored in stack
let str = 'hello'; // Stored in stack

// Reference type example
let obj = { a: 1 }; // Object stored in heap, obj holds reference to heap
let arr = [1, 2, 3]; // Array stored in heap, arr holds reference to heap

Variable Scope and Closures

Scope Chain is the mechanism JavaScript uses to resolve variable lookups, consisting of the current execution environment and all parent execution environments’ variable objects.

Closure is a combination of a function and its surrounding state (lexical environment), allowing the function to access variables outside its lexical scope.

function outer() {
  let outerVar = 'I am outer'; // Stored in outer's lexical environment

  function inner() {
    console.log(outerVar); // Accesses outer variable, forming a closure
  }

  return inner;
}

const closureFunc = outer();
closureFunc(); // Output: "I am outer"

At the low level, a closure maintains a reference to its lexical environment, even after the outer function has finished executing.

Underlying Principles of JavaScript Functions

Function Definition and Storage

JavaScript functions are special objects with the following characteristics:

  • Instances of the Function object.
  • Can have properties and methods like regular objects.
  • Can be invoked for execution.

Low-Level Representation of Function Creation:

function greet(name) {
  return `Hello, ${name}!`;
}

Internally equivalent to:

const greet = new Function('name', 'return `Hello, ${name}!`;');

Function Call Mechanism

A function call involves the following steps:

  1. Create an execution context.
  2. Create a variable environment.
  3. Establish the scope chain.
  4. Determine this binding.
  5. Execute the function body.
  6. Return the result (or implicitly return undefined).

Low-Level Process of a Function Call:

function add(a, b) {
  return a + b;
}

add(2, 3);

Internally equivalent to:

  1. Create a new execution context.
  2. Bind parameters a and b to the execution context’s variable environment.
  3. Execute the function body return a + b.
  4. Return the result 5.
  5. Destroy the execution context (subject to garbage collection).

this Binding Mechanism

The binding of this in JavaScript is complex and depends on how a function is called:

  1. Default Binding: Independent function call, this points to the global object (non-strict mode) or undefined (strict mode).
  2. Implicit Binding: Called as an object method, this points to the calling object.
  3. Explicit Binding: Using call, apply, or bind, this points to the specified object.
  4. New Binding: Called with new as a constructor, this points to the newly created object.
  5. Arrow Functions: this is inherited from the enclosing scope.
// Default binding
function defaultBind() {
  console.log(this); // Browser: window, Node.js: global
}
defaultBind();

// Implicit binding
const obj = {
  method: function() {
    console.log(this); // obj
  }
};
obj.method();

// Explicit binding
function explicitBind() {
  console.log(this); // Specified object
}
explicitBind.call({ custom: 'object' });

// New binding
function Person(name) {
  this.name = name;
}
const p = new Person('John');
console.log(p.name); // John

// Arrow function
const arrowFunc = () => {
  console.log(this); // Inherited from outer scope
};

Function Parameter Handling

JavaScript function parameters have the following characteristics:

  • Flexible number of parameters (supports variadic functions).
  • Parameters are passed by value (for objects, passed by sharing).
  • Supports default parameters.
  • Supports rest parameters.

Low-Level Implementation of Parameter Handling:

function example(a, b = 10, ...rest) {
  console.log(a, b, rest);
}
example(1, 2, 3, 4, 5);

Internally equivalent to:

function example(a, b, rest) {
  // Default parameter handling
  if (b === undefined) {
    b = 10;
  }

  // Rest parameter handling
  if (arguments.length > 2) {
    rest = Array.prototype.slice.call(arguments, 2);
  } else {
    rest = [];
  }

  console.log(a, b, rest);
}
example(1, 2, 3, 4, 5);

Function Hoisting and Function Expressions

Function declarations are hoisted, while function expressions are not:

// Function declaration (hoisted)
foo(); // "I am a function"
function foo() {
  console.log("I am a function");
}

// Function expression (not hoisted)
bar(); // TypeError: bar is not a function
var bar = function() {
  console.log("I am a function expression");
};

Low-Level Difference:

  • Function declarations are processed and added to the scope during compilation.
  • Function expressions are assigned during runtime.

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Share your love