# Week 3 – Task 1: Understanding JavaScript Core Concepts

작성자

카테고리:

← 피드로
DEV Community · koushikmaya · 2026-08-12 개발(SW)

Week 3 – Task 1: JavaScript Core Concepts

JavaScript has many concepts that look simple at first but become very important when writing real applications. While learning JavaScript, I came across concepts like var, let, const, hoisting, lexical scope, execution context, call stack, closures, and this.

In this blog, I’m sharing my understanding of these concepts with simple examples.

1. Variables: var, let, and const

Variables are used to store data in JavaScript.

var name = "Koushik";
let age = 20;
const city = "Hyderabad";

Enter fullscreen mode Exit fullscreen mode

Although all three can be used to declare variables, they behave differently.

var

var is the older way of declaring variables in JavaScript.

var age = 20;

age = 21;

console.log(age); // 21

Enter fullscreen mode Exit fullscreen mode

var is function-scoped.

if (true) {
    var x = 10;
}

console.log(x); // 10

Enter fullscreen mode Exit fullscreen mode

The variable is accessible outside the if block.

let

let is block-scoped.

if (true) {
    let x = 10;
}

console.log(x); // ReferenceError

Enter fullscreen mode Exit fullscreen mode

A variable declared with let can be reassigned.

let age = 20;

age = 21;

console.log(age); // 21

Enter fullscreen mode Exit fullscreen mode

But it cannot be redeclared in the same scope.

let age = 20;
let age = 21; // SyntaxError

Enter fullscreen mode Exit fullscreen mode

const

const is also block-scoped.

const age = 20;

Enter fullscreen mode Exit fullscreen mode

It cannot be reassigned.

const age = 20;

age = 21; // TypeError

Enter fullscreen mode Exit fullscreen mode

However, const does not make an object completely immutable.

const user = {
    name: "Koushik"
};

user.name = "Rahul";

console.log(user.name); // Rahul

Enter fullscreen mode Exit fullscreen mode

The object can still be modified. The variable itself cannot be reassigned to another object.

Quick Comparison

Feature var let const Scope Function Block Block Reassignment Yes Yes No Redeclaration Yes No No Hoisted Yes Yes Yes Temporal Dead Zone No Yes Yes

For modern JavaScript, let and const are generally preferred over var.

2. Hoisting

Hoisting is the behavior where JavaScript processes declarations before executing the code in that scope.

For example:

console.log(x);

var x = 10;

Enter fullscreen mode Exit fullscreen mode

The output is:

undefined

Enter fullscreen mode Exit fullscreen mode

This can be understood roughly as:

var x;

console.log(x);

x = 10;

Enter fullscreen mode Exit fullscreen mode

The declaration is available before the assignment happens.

let and const

Now consider:

console.log(x);

let x = 10;

Enter fullscreen mode Exit fullscreen mode

This gives:

ReferenceError

Enter fullscreen mode Exit fullscreen mode

let and const are also hoisted, but they cannot be accessed before their declaration is evaluated.

This period is called the Temporal Dead Zone (TDZ).

// TDZ starts

let x = 10;

// TDZ ends

Enter fullscreen mode Exit fullscreen mode

Trying to access x before its declaration causes a ReferenceError.

Function Hoisting

Function declarations are also hoisted.

sayHello();

function sayHello() {
    console.log("Hello");
}

Enter fullscreen mode Exit fullscreen mode

Output:

Hello

Enter fullscreen mode Exit fullscreen mode

However, function expressions assigned to let or const cannot be called before their declaration.

sayHello();

const sayHello = function () {
    console.log("Hello");
};

Enter fullscreen mode Exit fullscreen mode

This results in a ReferenceError.

3. Lexical Scope

Lexical scope means that the scope of a variable is determined by where the code is written.

Consider:

let name = "Koushik";

function outer() {
    let age = 20;

    function inner() {
        console.log(name);
        console.log(age);
    }

    inner();
}

outer();

Enter fullscreen mode Exit fullscreen mode

The inner() function can access variables from its own scope and from the outer scopes.

The scope chain looks like:

inner()
   ↓
outer()
   ↓
global scope

Enter fullscreen mode Exit fullscreen mode

This is called the scope chain.

JavaScript uses lexical scoping, which is also one of the main reasons closures work.

4. Execution Context

An execution context is the environment in which JavaScript code is executed.

There are mainly two execution contexts that are important to understand:

  • Global Execution Context
  • Function Execution Context

When JavaScript starts running a program, it creates the Global Execution Context.

For example:

let name = "Koushik";

function greet() {
    console.log("Hello");
}

greet();

Enter fullscreen mode Exit fullscreen mode

First, JavaScript creates the global execution context.

When greet() is called, JavaScript creates another execution context for that function.

Global Execution Context
          |
          ↓
       greet()
          |
          ↓
Function Execution Context

Enter fullscreen mode Exit fullscreen mode

Creation and Execution

Execution can be simplified into two phases:

  • Creation phase
  • Execution phase

During the creation phase, JavaScript prepares the environment for variables, functions, scope information, and this.

During the execution phase, JavaScript executes the code.

5. Call Stack

The call stack keeps track of which functions are currently being executed.

Consider:

function one() {
    console.log("One");
}

function two() {
    one();
    console.log("Two");
}

two();

Enter fullscreen mode Exit fullscreen mode

When two() is called, it is pushed onto the call stack.

Then two() calls one(), so one() is pushed on top.

| one()     |
| two()     |
| global    |
------------

Enter fullscreen mode Exit fullscreen mode

one() finishes first, so it is removed.

| two()     |
| global    |
------------

Enter fullscreen mode Exit fullscreen mode

Then two() finishes.

The call stack follows LIFO:

Last In, First Out

This is why the most recently called function finishes first.

Recommended Video

For a clearer understanding of Execution Context and the Call Stack, I recommend watching this YouTube tutorial:

Execution Context & Call Stack – JavaScript Tutorial

This video helped me understand how JavaScript executes code and how the call stack manages function calls.

6. Closures

Closures are one of the most interesting concepts in JavaScript.

A closure happens when a function remembers variables from its outer lexical scope even after the outer function has finished executing.

Example:

function outer() {
    let count = 0;

    function inner() {
        count++;
        console.log(count);
    }

    return inner;
}

const counter = outer();

counter(); // 1
counter(); // 2
counter(); // 3

Enter fullscreen mode Exit fullscreen mode

At first, outer() finishes execution.

Normally, we might expect count to disappear.

But inner() still needs access to count.

Because inner() was created inside outer(), it remembers the environment where it was created.

outer()
   |
   | count = 0
   |
   └── inner()
          |
          └── remembers count

Enter fullscreen mode Exit fullscreen mode

This is a closure.

Why Are Closures Useful?

Closures are commonly used for:

  • Maintaining state
  • Data privacy
  • Counters
  • Function factories
  • Callbacks
  • Event handlers

For example:

function createCounter() {
    let count = 0;

    return function () {
        count++;
        return count;
    };
}

const counter = createCounter();

console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

Enter fullscreen mode Exit fullscreen mode

The count variable is private to the closure.

7. Understanding this

The this keyword is another important JavaScript concept.

A common beginner mistake is thinking:

this always refers to the object where the function was created.”

For normal functions, this is not necessarily true.

The value of this depends mainly on how the function is called.

Some important cases are:

  • Implicit binding
  • Explicit binding
  • new binding
  • Arrow functions

8. Implicit Binding

When a function is called as a method of an object, this refers to that object.

const user = {
    name: "Koushik",

    greet() {
        console.log(this.name);
    }
};

user.greet();

Enter fullscreen mode Exit fullscreen mode

Output:

Koushik

Enter fullscreen mode Exit fullscreen mode

Here:

this === user

Enter fullscreen mode Exit fullscreen mode

because the function was called as:

user.greet();

Enter fullscreen mode Exit fullscreen mode

The object before the dot determines the this value in this case.

9. Explicit Binding

JavaScript provides three methods that can be used to control this:

  • call()
  • apply()
  • bind()

call()

function greet() {
    console.log(this.name);
}

const user = {
    name: "Koushik"
};

greet.call(user);

Enter fullscreen mode Exit fullscreen mode

Output:

Koushik

Enter fullscreen mode Exit fullscreen mode

call() immediately invokes the function with the specified this.

apply()

apply() works similarly to call(), but arguments are passed as an array.

function introduce(age, city) {
    console.log(this.name, age, city);
}

const user = {
    name: "Koushik"
};

introduce.apply(user, [20, "Hyderabad"]);

Enter fullscreen mode Exit fullscreen mode

bind()

bind() creates a new function with this permanently bound to the provided object.

function greet() {
    console.log(this.name);
}

const user = {
    name: "Koushik"
};

const newGreet = greet.bind(user);

newGreet();

Enter fullscreen mode Exit fullscreen mode

Output:

Koushik

Enter fullscreen mode Exit fullscreen mode

The simple difference is:

call()  → calls the function immediately
apply() → calls the function immediately
bind()  → returns a new function

Enter fullscreen mode Exit fullscreen mode

10. new Binding

The new keyword creates a new object and makes that object the this value inside the constructor function.

function User(name) {
    this.name = name;
}

const user1 = new User("Koushik");

console.log(user1.name);

Enter fullscreen mode Exit fullscreen mode

Output:

Koushik

Enter fullscreen mode Exit fullscreen mode

Conceptually, this happens:

new User("Koushik")
        |
        ↓
Create a new object
        |
        ↓
this points to that object
        |
        ↓
this.name = "Koushik"
        |
        ↓
Object is returned

Enter fullscreen mode Exit fullscreen mode

So user1 becomes an instance of User.

11. Arrow Functions and this

Arrow functions behave differently from normal functions.

An arrow function does not have its own this.

Instead, it inherits this from its surrounding lexical scope.

For example:

const user = {
    name: "Koushik",

    greet() {
        setTimeout(() => {
            console.log(this.name);
        }, 1000);
    }
};

user.greet();

Enter fullscreen mode Exit fullscreen mode

The arrow function gets this from greet().

greet()
   |
   | this → user
   |
   └── arrow function
          |
          └── inherits this → user

Enter fullscreen mode Exit fullscreen mode

Therefore, the output is:

Koushik

Enter fullscreen mode Exit fullscreen mode

This is one reason arrow functions are very useful for callbacks.

However, arrow functions should not be used when you specifically need a function to have its own dynamic this.

12. Putting the Concepts Together

Let’s look at an example that combines lexical scope, execution context, closures, and the call stack.

var name = "Global";

function outer() {
    let name = "Outer";

    function inner() {
        console.log(name);
    }

    return inner;
}

const fn = outer();

fn();

Enter fullscreen mode Exit fullscreen mode

When the program starts, JavaScript creates the global execution context.

Then:

const fn = outer();

Enter fullscreen mode Exit fullscreen mode

calls outer().

A new function execution context is created.

Inside outer():

let name = "Outer";

Enter fullscreen mode Exit fullscreen mode

Then inner() is created.

outer() returns inner.

const fn = outer();

Enter fullscreen mode Exit fullscreen mode

Now fn refers to inner.

When we call:

fn();

Enter fullscreen mode Exit fullscreen mode

inner() can still access:

name = "Outer"

Enter fullscreen mode Exit fullscreen mode

even though outer() has already finished.

That’s because of the closure.

The output is:

Outer

Enter fullscreen mode Exit fullscreen mode

Final Mental Model

These concepts are connected.

JavaScript starts
       |
       ↓
Global Execution Context
       |
       ↓
Code starts executing
       |
       ↓
Function is called
       |
       ↓
New Execution Context
       |
       ↓
Function pushed onto Call Stack
       |
       ↓
Variables resolved using Lexical Scope
       |
       ↓
Nested functions can create Closures
       |
       ↓
Function finishes
       |
       ↓
Execution Context removed from Call Stack
       |
       ↓
Closure can keep required variables accessible

Enter fullscreen mode Exit fullscreen mode

For this, remember:

How was the function called?
          |
    ┌─────┼─────────┬────────┐
    ↓     ↓         ↓        ↓
 obj.fn  call/apply  new     arrow
    ↓     ↓         ↓        ↓
implicit explicit   new    lexical
binding  binding    this     this

Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • var is function-scoped, while let and const are block-scoped.
  • Hoisting happens when JavaScript sets up a scope before executing it.
  • let and const have a Temporal Dead Zone.
  • Lexical scope is determined by where the code is written.
  • Execution contexts provide the environment for code execution.
  • The call stack manages function execution using LIFO.
  • Closures allow functions to remember variables from their outer scope.
  • this depends on how a normal function is called.
  • call(), apply(), and bind() provide explicit control over this.
  • new creates a new object and binds this to it.
  • Arrow functions don’t have their own this; they inherit it from the surrounding scope.

These concepts helped me understand that JavaScript is not just about writing statements one after another. There is a whole execution process happening behind the scenes, and understanding that process makes it much easier to reason about JavaScript code and debug unexpected behavior.
Also if anyone wants my handwritten notes on this core topic you can comment on this blog. Thank You!

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다