Назад в блог
Фронтенд

JavaScript and TypeScript: One Language, Two Disciplines

The parts of JavaScript that still surprise people, what TypeScript actually buys you, and the type-system features worth learning past `any`.

Опубликовано
Время чтения
10 мин чтения
Автор
Yakhya

JavaScript is the only language that runs natively in every browser, which means it was never allowed to break compatibility and has therefore accumulated a decade of design decisions it cannot take back. TypeScript is the industry's answer: a type layer that erases at build time, adds no runtime cost, and changes how large codebases feel to work in. Using both well means understanding what each one is responsible for.

The JavaScript that still catches people

  • Closures — functions capture variables, not values. This is the mechanism behind hooks, module privacy, and most memory leaks in long-lived listeners.
  • `this` is determined by the call site, not the definition site, unless you use an arrow function, which captures it lexically. Nearly every `this is undefined` bug is this rule.
  • The event loop: synchronous code, then microtasks (promises), then macrotasks (timers). Same model as the browser and Node, and worth being able to trace by hand.
  • Equality: `===` always, except `== null` which usefully catches both null and undefined.
  • Prototypes: classes are syntax over prototype chains. You rarely need this until you are debugging a library, at which point you need it badly.
  • Modules are hoisted and static — import order does not depend on runtime, which is why tree shaking works at all.
javascript
// Closures capture the binding, not the snapshot
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i));   // 3 3 3
for (let i = 0; i < 3; i++) setTimeout(() => console.log(i));   // 0 1 2

// Optional chaining + nullish coalescing: the two additions
// that removed the most defensive noise from real code
const port = config?.server?.port ?? 3000;   // 0 stays 0, unlike ||

What TypeScript is actually for

It is not about catching typos. The real return is that types are executable documentation that cannot drift, refactoring becomes mechanical instead of archaeological, and the editor can answer "what is this and where does it come from" instantly. On a codebase with more than a couple of contributors, that changes the cost of change — which is the cost that actually dominates a project's life.

The critical thing to internalize: TypeScript disappears at runtime. It will not validate an API response, a form submission, or a JSON file. Anything crossing your program's boundary must be validated at runtime — Zod, Valibot, or a hand-written guard — and the type derived from that validator, not asserted alongside it.

typescript
import { z } from "zod";

const User = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  role: z.enum(["admin", "member"]),
});

type User = z.infer<typeof User>;          // one source of truth

export async function getUser(id: string): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error(`getUser failed: ${res.status}`);
  return User.parse(await res.json());     // fails loudly at the boundary
}

The features worth learning past the basics

  • Discriminated unions — model state as `{ status: 'loading' } | { status: 'error'; error: Error } | { status: 'ok'; data: T }` and let exhaustiveness checking prove you handled every case.
  • `unknown` instead of `any` — it forces a narrowing step, which is exactly the check `any` skips.
  • Type guards and `satisfies` — the first narrows at runtime, the second validates a literal against a type without widening it.
  • Utility types: Pick, Omit, Partial, Record, ReturnType, Awaited. These express relationships instead of duplicating shapes.
  • Generics with constraints — write them when a function genuinely relates its input and output types, and not to look clever.
  • `strict: true` from day one. Retrofitting strictness onto a large codebase is a project; starting with it is free.
A type that requires a comment to explain it is usually a design that should have been simpler.

Practical stance

Model your domain so illegal states cannot be represented, validate at every boundary, keep `any` out of the codebase with a lint rule, and resist the temptation to build type-level machinery that only its author can maintain. TypeScript rewards ordinary, boring, precise types. The clever ones cost more to read than the bugs they prevent.

And keep learning the JavaScript underneath. TypeScript makes it safer to work with; it does not make it unnecessary to understand. Every performance problem, every async ordering surprise, every memory leak is still a JavaScript problem when you get to the bottom of it.

Теги
JavaScriptTypeScriptTypesToolingWeb
Продолжить чтениеВсе статьи
Yakhya
© 2026 Yakhya. Все права защищены.