What is JavaScript? A Complete Guide for Beginners

node:internal/modules/cjs/loader:1147
throw err;
^

Error: Cannot find module ‘left-pad’
Require stack:
– /opt/legacy-app/dist/server/index.js
at Module._resolveFilename (node:internal/modules/cjs/loader:1144:15)
at Module._load (node:internal/modules/cjs/loader:985:27)
at Module.require (node:internal/modules/cjs/loader:1235:19)
at require (node:internal/modules/helpers:176:18)
at Object. (/opt/legacy-app/dist/server/index.js:42:18)
at Module._compile (node:internal/modules/cjs/loader:1356:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1414:10)
at Module.load (node:internal/modules/cjs/loader:1208:32)
at Module._load (node:internal/modules/cjs/loader:1024:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:11) {
code: ‘MODULE_NOT_FOUND’,
requireStack: [ ‘/opt/legacy-app/dist/server/index.js’ ]
}

[3:14 AM] FATAL: Build failed. 482 vulnerabilities (112 critical).
[3:14 AM] WARNING: The ‘request’ package is deprecated.
[3:14 AM] ERROR: JavaScript heap out of memory.

## I. The Triage: Defining the Beast

I’ve been staring at this blinking cursor for three hours. The terminal is bleeding red, and the heap is bloated like a drowned corpse. This is the reality of maintaining a half-million-line monolith that started its life when ES5 was considered "the new thing" and IE8 was still a nightmare we had to support. Every time I try to run a clean `npm install` on this Node.js v20.11.1 environment, the dependency tree groans under the weight of a thousand forgotten developers.

People ask me, usually juniors with stars in their eyes and a fresh copy of some "Learn React in 24 Hours" book, **what is** JavaScript? They want a clean definition. They want me to say it’s a high-level, interpreted language with first-class functions. But that’s the textbook lie. 

The truth is that JavaScript is a historical accident that refused to die. It’s a language that was famously prototyped in ten days by Brendan Eich at Netscape, meant to be a "glue language" for designers to make things blink. It wasn't supposed to run the world's financial systems. It wasn't supposed to handle server-side logic via Node.js. It wasn't supposed to be the target for every transpiler on the planet. Yet, here we are. It is the assembly language of the web because it won the war of attrition. It’s the only thing that runs everywhere, so we’ve spent the last thirty years duct-taping features onto it until it became the multi-paradigm Frankenstein’s monster we see today.

When you ask **what is** this language at its core, you’re looking at a single-threaded, non-blocking, event-driven runtime that manages to feel like it’s doing everything at once while actually doing only one thing at a time, very quickly, and often incorrectly if you don't understand the underlying mechanics.

## II. The Prototype Chain: A Legacy of Regret

Before we had the `class` keyword—which is just syntactic sugar designed to stop Java developers from crying—we had the prototype chain. In this 500k-line codebase, I’m still finding "classes" defined by monkey-patching `Function.prototype`. It’s a mess of `this` binding and `call()` and `apply()` that makes modern "clean code" look like a joke.

In JavaScript, inheritance isn't about blueprints; it's about a linked list of objects. When you look up a property, the engine crawls up the `__proto__` link until it finds it or hits `null`. It’s elegant in a "minimalist architecture" sort of way, but in practice, it leads to performance bottlenecks and "where the hell did this method come from?" debugging sessions at 3 AM.

The transition from ES5 to ES6 (ES2015) tried to hide this with the `class` syntax, but the underlying behavior remains. If you don't understand that `super()` is just a wrapper for prototype delegation, you’re going to leak memory or break the context of `this`. And don't even get me started on `this`. It’s the most misunderstood keyword in the history of computing. Its value depends on *how* a function is called, not where it’s defined—unless you’re using arrow functions, which lexically bind it. We have three different ways to define a function, and they all behave differently. It’s a minefield.

## III. V8 and the Myth of Performance

We’re running on V8, Google’s pride and joy. It’s a marvel of engineering, but it’s also the reason why developers think they can get away with writing garbage code. V8 uses a JIT (Just-In-Time) compiler. It takes your source code, turns it into bytecode via the Ignition interpreter, and then, if a piece of code is "hot" (run frequently), the TurboFan compiler kicks in to turn it into highly optimized machine code.

But V8 is picky. It relies on "Hidden Classes" (or Shapes) to optimize object access. If you keep changing the structure of your objects—adding properties on the fly, deleting them, changing types—you’re de-optimizing the engine. You’re forcing it to drop back down to the interpreter. You’re killing your throughput.

Then there’s the Call Stack and the Task Queue. This is the "Event Loop" that everyone talks about but few actually visualize. The Call Stack is where your synchronous code lives. When it’s empty, the loop looks at the Microtask Queue (Promises, `process.nextTick`) and then the Macrotask Queue (`setTimeout`, `setImmediate`, I/O). 

If you block the stack with a heavy computation—like trying to parse a 50MB JSON blob in the main thread—the whole UI freezes. The "non-blocking" nature of JavaScript is a cooperative agreement. If you don't play nice, the whole system grinds to a halt.

## IV. The Garbage Collector: Where Memory Goes to Die

The V8 Garbage Collector (GC) is a generational, stop-the-world collector. It divides memory into "New Space" and "Old Space." Most objects die young (the Generational Hypothesis), so they get cleared out of New Space quickly using a Scavenge algorithm. If they survive long enough, they get promoted to Old Space, where the Mark-and-Sweep and Mark-and-Compact algorithms take over.

The problem in this legacy codebase is that we have "lingering" references. A global variable here, a forgotten event listener there, a closure that captures a massive object it doesn't need. The GC can't reclaim memory if something is still reachable.

Here is a classic example of a memory leak that I just found in our notification service:

```javascript
// A classic memory leak: The "Forgotten Closure"
const leakyData = [];

function createNotification(message) {
    const bigData = new Array(1000000).fill('💀'); // Massive allocation

    return function() {
        // This closure keeps 'bigData' in scope even if we only need 'message'
        console.log(`Notification: ${message}`);
        // If we don't nullify bigData or structure this better, 
        // bigData stays in memory as long as this returned function exists.
        return bigData.length; 
    };
}

setInterval(() => {
    // We push these closures into a global array and never clear it.
    // The GC sees 'leakyData' is reachable, so every 'bigData' instance stays alive.
    leakyData.push(createNotification("System Overload"));
}, 100);

In a long-running Node.js process, this is a death sentence. The heap grows until the OOM (Out of Memory) killer steps in and nukes the process. We’ve been seeing this in production every 48 hours. My “fix” for now is a cron job that restarts the service, which is the industry standard for “I’m too tired to find every leak in this 10-year-old spaghetti.”

V. The Async Evolution: From Hell to Purgatory

The history of JavaScript is the history of trying to handle things that take time without freezing the browser. In the beginning, we had callbacks. It was fine for a simple “click this, do that.” But then we started nesting them. We ended up in “Callback Hell” or the “Pyramid of Doom.”

Look at this artifact I found in our legacy authentication module. It’s a masterpiece of unreadable garbage:

// Exhibit A: Callback Hell (circa 2012)
function verifyUser(userId, password, callback) {
    db.users.findById(userId, (err, user) => {
        if (err) return callback(err);
        if (!user) return callback(new Error("User not found"));

        crypto.pbkdf2(password, user.salt, 10000, 64, 'sha512', (err, derivedKey) => {
            if (err) return callback(err);

            if (user.hash === derivedKey.toString('hex')) {
                db.logs.logLogin(userId, (err) => {
                    if (err) return callback(err);

                    permissions.getRoles(userId, (err, roles) => {
                        if (err) return callback(err);
                        callback(null, { user, roles });
                    });
                });
            } else {
                callback(new Error("Invalid password"));
            }
        });
    });
}

Every if (err) is a chance to forget a return and execute the callback twice. Every level of nesting makes it harder to reason about scope. Then came Promises, which flattened the pyramid but introduced “Promise Hell” where people would just chain .then() calls until they lost track of the return values.

Finally, we got async/await in ES2017. It’s much cleaner, but it’s just a wrapper around Promises. It doesn’t magically make the code multi-threaded. It just makes the asynchronous code look synchronous.

// Exhibit B: Modern Async/Await (The Refactor)
async function verifyUser(userId, password) {
    try {
        const user = await db.users.findById(userId);
        if (!user) throw new Error("User not found");

        const derivedKey = await crypto.pbkdf2Async(password, user.salt, 10000, 64, 'sha512');

        if (user.hash !== derivedKey.toString('hex')) {
            throw new Error("Invalid password");
        }

        await db.logs.logLogin(userId);
        const roles = await permissions.getRoles(userId);

        return { user, roles };
    } catch (err) {
        logger.error(`Auth failed: ${err.message}`);
        throw err; // Re-throw for the caller to handle
    }
}

It looks better, but it’s still the same event loop underneath. If db.users.findById takes 5 seconds because someone forgot an index in MongoDB, your event loop is still waiting, even if it’s “non-blocking.”

VI. The NPM Ecosystem: Dependency Purgatory

We cannot talk about what is JavaScript without acknowledging the NPM ecosystem. It is the largest package registry in existence, and it is a terrifying monument to human laziness. We have a dependency for everything. is-odd, is-array, left-pad.

When I run npm install on this project, it downloads 2.5 gigabytes of code into node_modules. Most of that is transitive dependencies—packages required by packages required by packages. It’s a “tower of cards built on a swamp.” If one developer in Ohio decides to un-publish a utility library because they’re having a bad day, half the internet breaks. We saw it with left-pad. We saw it with the event-stream compromise.

The TC39 proposal process—the group that decides the future of the language—moves slowly for a reason. They have to maintain “web compatibility.” You can’t break the web. Code written in 1995 must still run in 2024. This means we can never truly remove the bad parts of the language; we can only add new, “better” parts on top of them. This leads to a massive cognitive load. To be a “Senior” dev, you have to know the new way, the old way, and the “we don’t talk about that” way (like with statements or eval).

We use transpilation (Babel) and bundling (Webpack, Rollup, Vite) to bridge the gap between the code we want to write and the code the browser can actually execute. We’ve added layers of abstraction—TypeScript for types, JSX for templating—until the original language is buried under a mountain of tooling. We spend 40% of our time configuring build pipelines instead of writing logic.

VII. The TC39 Process and the Assembly Language of the Web

The TC39 (Technical Committee 39) is the body that governs ECMAScript. Their proposal process (Stage 0 through Stage 4) is how we get new features like Optional Chaining (?.) or Nullish Coalescing (??). It’s a rigorous process, but it’s also a political one. Everyone wants their favorite feature in the language, but the more we add, the more complex the engines have to become.

JavaScript has become the “assembly language” because of things like WebAssembly (Wasm). While Wasm is great for performance-critical tasks, JavaScript remains the orchestrator. It’s the glue. It’s the language that manages the DOM, handles the fetch requests, and coordinates the workers.

But the cost of this dominance is “JavaScript Fatigue.” The ecosystem moves so fast that by the time you’ve mastered a framework, it’s already being replaced by something “faster” and “lighter” that usually just re-invents the same patterns with different names. We went from server-side rendering to client-side SPAs, and now we’re moving back to server components and “hydration” strategies. It’s a circle. We’re just moving the complexity around.

VIII. The Final Log Entry: 4:00 AM

The build finally passed. I had to manually override a peer dependency conflict and delete the package-lock.json file—a move that will surely haunt me tomorrow when the CI/CD pipeline fails.

What is JavaScript? It’s a survivor. It’s a language that was never meant to do what it’s doing, yet it does it anyway. It’s the source of my paycheck and the source of my grey hair. It’s a chaotic, beautiful, broken mess that powers the modern world.

We’re stuck with it. We can’t rewrite the web in Rust (though some are trying). We can’t go back to static HTML. We have to keep patching the swamp, keep optimizing the hidden classes, and keep praying that the garbage collector kicks in before the server runs out of RAM.

I’m going to get some coffee. The heap snapshot shows a 200MB leak in the legacy WebSocket handler. That’s a problem for 9:00 AM. For now, the logs are quiet, and the “tower of cards” is standing. Barely.

“`bash
[4:02 AM] INFO: Server listening on port 3000
[4:02 AM] DEBUG: Initializing legacy middleware…
[4:02 AM] WARN: Memory usage at 85% capacity.
[4:02 AM] SUCCESS: Deployment complete. (14,203 warnings ignored)

Related Articles

Explore more insights and best practices:

Leave a Comment