Why JavaScript Is More Than Just a Scripting Language — My Journey into Its Core Concepts
From Hoisting to the Event Loop, Closures to Prototypes — A Deep Dive Into JavaScript Theory That Every Developer Should Master (But Often…
Why JavaScript Is More Than Just a Scripting Language — My Journey into Its Core Concepts
From Hoisting to the Event Loop, Closures to Prototypes — A Deep Dive Into JavaScript Theory That Every Developer Should Master (But Often Skips).

I’ll be honest: I used JavaScript for years without really understanding what was going on under the hood. I copied patterns, memorized syntax, and Googled my way out of callback hell. It worked… until it didn’t.
Then I hit a wall.
Suddenly, performance broke. Bugs appeared out of nowhere. this started acting weird. And async behavior? Black magic.
That’s when I realized: JavaScript is simple on the surface — but insanely powerful underneath.
This article is not a tutorial. It’s a theoretical deep-dive into JavaScript’s actual behavior — the things that explain why your code works (or breaks). We’ll go through the fundamental concepts I had to re-learn the hard way — and trust me, you want to learn them now rather than later.
1. Execution Context: The Stage Where Everything Happens
Before your code does anything, JavaScript prepares an execution context. Think of it like setting the stage before the play starts.
There are three main types of execution contexts:
- Global Execution Context — This is created when your JS file first runs. It’s where global variables and functions live.
- Function Execution Context — Every time a function is invoked, a new one is created.
- Eval Execution Context — Rarely used (and often discouraged).
The lifecycle of an execution context:
- Creation Phase: Variables and functions are hoisted, and
thisis determined. - Execution Phase: Code runs line-by-line.
Each context has its own variable environment, scope chain, and **this binding**.
let a = 10;
function outer() {
let b = 20;
function inner() {
let c = 30;
console.log(a + b + c);
}
inner();
}
outer(); // 60
The inner() function has access to all variables in its lexical scope — more on that in a bit.
2. Hoisting: What’s Pulled Up and What Isn’t
Hoisting is JavaScript’s way of “lifting” function and variable declarations to the top of their scope during the creation phase.
But here’s the catch:
varis hoisted and initialized as undefinedletandconstare hoisted but not initialized- Function declarations are fully hoisted
- Function expressions are not
console.log(a); // undefined
var a = 5;
console.log(b); // ReferenceError
let b = 10;
foo(); // works
function foo() {
console.log('I’m hoisted');
}
bar(); // TypeError
var bar = function () {
console.log('I’m not hoisted');
}
Once you understand hoisting, you start debugging 10x faster.
3. Closures: Where Functions Remember Things They Shouldn’t
Closures happen when a function remembers the variables from its lexical scope — even if that scope has already finished executing.
This is what makes things like private variables and function factories possible.
function counter() {
let count = 0;
return function () {
count++;
return count;
};
}
const increment = counter();
console.log(increment()); // 1
console.log(increment()); // 2
console.log(increment()); // 3
Each call to counter() creates a new closure, keeping count safe from the outside.
This concept is core to async logic, React hooks, and functional programming in JS.
4. The Event Loop: Async Without the Confusion
JavaScript is single-threaded. So how does it handle async operations?
👉 The Event Loop.
Here’s what happens under the hood:
- Your code runs line-by-line (synchronously).
- Async tasks like
setTimeout, Promises, orfetch()are handed off to the Web APIs (provided by the browser or Node). - When those tasks finish, callback functions are placed in either the macro-task queue or micro-task queue.
- The Event Loop picks up the next task only when the call stack is empty.
console.log('Start');
setTimeout(() => {
console.log('Timeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise');
});
console.log('End');
Output:
Start
End
Promise
Timeout
Why? Because Promise callbacks (microtasks) run before setTimeout (macrotasks).
5. this Keyword: Context Is Everything
The value of this in JavaScript depends entirely on how a function is called.
Let’s break it down:
const obj = {
name: 'JavaScript',
greet() {
console.log(`Hello from ${this.name}`);
},
};
obj.greet(); // Hello from JavaScript
const greetFn = obj.greet;
greetFn(); // Hello from undefined (in strict mode)
const boundFn = obj.greet.bind(obj);
boundFn(); // Hello from JavaScript
this changes in:
- Arrow functions (lexically bound)
- Event listeners
- Class methods
- setTimeouts
- DOM interactions
Arrow functions don’t get their own this. They inherit it from the parent context.
6. Prototypes: The Secret Behind JavaScript Inheritance
JavaScript doesn’t use classical OOP inheritance. It uses prototype chaining.
Every object has an internal link to another object, its prototype, which forms a chain.
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function () {
console.log(`${this.name} makes a noise`);
};
const dog = new Animal('Dog');
dog.speak(); // Dog makes a noise
dog doesn’t have a speak method. But JavaScript finds it up the prototype chain.
This is also how array and string methods work:
const arr = [1, 2, 3];
arr.toString(); // comes from Array.prototype
7. Modules and Scope Isolation
Before ES6, we faked modules with IIFEs. Now we use proper import / export syntax.
ES6 Module:
// math.js
export function add(a, b) {
return a + b;
}
// main.js
import { add } from './math.js';
console.log(add(2, 3));
Modules are:
- Lazy-loaded
- Scoped (no pollution of global namespace)
- Strict mode by default
They’re also the foundation of bundling tools like Webpack and Vite.
8. Garbage Collection and Memory Leaks
JavaScript uses automatic garbage collection, which means memory that’s no longer “reachable” gets cleaned up.
But memory leaks still happen.
Common causes:
- Global variables that never get cleared
- Closures that retain too much memory
- DOM elements that aren’t properly removed
Avoid this by:
- Removing event listeners
- Avoiding unnecessary object retention
- Profiling memory in DevTools
let leaking = {};
function leak() {
leaking.largeArray = new Array(1000000).fill('*');
}
This will retain memory unless leaking is cleared manually.
9. Strict Mode and Why It Matters
'use strict' makes JavaScript… well, stricter.
Benefits:
- Prevents accidental globals
- Throws errors on silent failures
- Makes
thisundefined in standalone functions
Always use it:
'use strict';
function test() {
undeclaredVar = 5; // ReferenceError
}
Strict mode turns potentially dangerous behavior into safe, debuggable errors.

Final Thoughts
JavaScript isn’t just for DOM scripts and quick hacks. It’s a full-featured, functional, object-oriented language with a runtime model that’s genuinely unique.
Once you understand these theoretical foundations:
- Async programming becomes easier
- Bugs become easier to fix
- Code becomes easier to scale
A message from our Founder
Hey, Sunil here. I wanted to take a moment to thank you for reading until the end and for being a part of this community.
Did you know that our team run these publications as a volunteer effort to over 200k supporters? We do not get paid by Medium!
If you want to show some love, please take a moment to follow me on LinkedIn, TikTok and Instagram. And before you go, don’t forget to clap and follow the writer️!
메타데이터
- post_id
- ea2d5691082d
- slug
- why-javascript-is-more-than-just-a-scripting-language-my-journey-into-its-core-concepts-ea2d5691082d
- url
- https://javascript.plainenglish.io/why-javascript-is-more-than-just-a-scripting-language-my-journey-into-its-core-concepts-ea2d5691082d
- canonical_url
- https://javascript.plainenglish.io/why-javascript-is-more-than-just-a-scripting-language-my-journey-into-its-core-concepts-ea2d5691082d
- author_url
- https://medium.com/@maximilianoliver25
- status
- ok
- fetched_at
- 2026-08-24 16:46:10