Estágio 01 · 01-07
LockedJavaScript é a linguagem mais usada no mundo, e a mais mal-entendida: porque a maioria dos devs aprende sintaxe sem nunca olhar o que está embaixo. Você usa Promise, async/await, setTimeout, closure, this, class, mas se eu te perguntar:
setTimeout(fn, 0) ou Promise.resolve().then(fn)?"this aponta dentro de um arrow function?"[1,2,3] + [4,5,6] retorna '1,2,34,5,6'?"Sem respostas precisas a essas perguntas, você não domina JavaScript. E como JS sustenta Node, React, Next.js, todo seu stack daqui pra frente vira castelo na areia.
Este módulo te dá o JS de verdade. Depois dele, TypeScript (01-08) vira evolução natural.
O V8 (engine do Chrome/Node) executa JS em 5 passos simplificados:
Hidden classes (shapes): quando V8 vê um objeto, cria uma "shape" descrevendo seu layout (offsets das propriedades). Objetos com mesma shape compartilham. Adicionar/remover propriedade muda a shape → invalida hidden class → invalida código otimizado.
Lição prática:
Inline caches (ICs): ao chamar obj.foo, V8 cacheia "shape do obj + offset do foo". Próximas chamadas com mesma shape são quase grátis. Diferentes shapes → IC vira "polymorphic" (3-4 shapes) → "megamorphic" (>4) → cai pra slow path.
GC (Garbage Collector) do V8, generational:
Implicação: quanto mais "lixo" você gera (alocações temporárias), mais GC roda. Profile com --trace-gc se suspeitar.
Primitivos (passados por valor): number, string, boolean, null, undefined, bigint, symbol.
Reference (passados por referência): object, array, function (que é object).
let a = 1; let b = a; b = 2; // a = 1, b = 2
let x = {n:1}; let y = x; y.n = 2; // x.n = 2, y.n = 2 (mesma ref)
Strings são imutáveis (não pode mudar caractere por índice). "abc"[0] = "z" falha silenciosamente em sloppy mode, throw em strict.
Number em JS é IEEE 754 double-precision (64-bit float). Por isso 0.1 + 0.2 !== 0.3. Pra dinheiro, use bigint ou strings ou libs (big.js, decimal.js).
BigInt (ES2020): inteiros arbitrariamente grandes. 100n. Não interopera com number direto.
Symbol: identificador único. Symbol('foo') !== Symbol('foo'). Usado pra propriedades não-enumeráveis, como Symbol.iterator.
JS converte tipos automaticamente em muitos contextos. Regras precisas estão na spec ECMA-262, seção ToPrimitive/ToNumber/ToString.
== vs ===:
=== (strict): tipo igual, valor igual. Use sempre.== (loose): tenta coercion antes. Inferno em casos: null == undefined (true), 0 == '' (true), [] == false (true).+ com string: concatena. Senão soma.
1 + 1 // 2
1 + '1' // '11'
1 + null // 1 (null → 0)
1 + undefined // NaN (undefined → NaN)
[] + [] // '' (each → '')
[] + {} // '[object Object]'
[1,2,3] + [4,5,6] → ToPrimitive([1,2,3]) = "1,2,3"; mesma coisa pro outro; concatena → "1,2,34,5,6". Spec literal.
Truthy/falsy:
false, 0, -0, 0n, '', null, undefined, NaN. Tudo.[], {}, '0', 'false').Booleanização:
if (x), !!x, Boolean(x) → todas usam ToBoolean.[] é truthy. Boolean([]) = true. if ([]) executa o if.Escopo léxico: o escopo de uma variável é determinado por onde ela é definida no código, não onde é chamada.
const x = 10;
function outer() {
const y = 20;
function inner() {
return x + y; // ambos visíveis: léxico
}
return inner;
}
const fn = outer();
fn(); // 30, mesmo que `outer` já retornou, `inner` capturou o ambiente
Closure = função + ambiente léxico capturado.
Esse é o motor de:
const counter = (() => {
let count = 0;
return { inc: () => ++count, get: () => count };
})();
useState retorna ref ao state guardado no fiber via closure interna.Bug clássico do var em loop:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// imprime: 3, 3, 3, todas as closures compartilham `i` (function-scoped)
Soluções:
let i (block-scoped → cada iteração tem novo i na closure).setTimeout((function(j){ return () => console.log(j); })(i), 0);Array.from/forEach com index.this, o bicho de 7 cabeçasthis é o contexto de uma chamada. Determinado por como a função é chamada, não onde definida.
| Forma de chamada | this |
|---|---|
obj.method() | obj |
fn() (standalone) | undefined (strict) ou globalThis (sloppy) |
new Constructor() | objeto novo |
fn.call(ctx), fn.apply(ctx, args) | ctx |
fn.bind(ctx)() | ctx (fixo) |
| Arrow function | this do escopo léxico (não tem próprio) |
Arrow functions não têm this, arguments, super, new.target próprios. Capturam do escopo. Por isso setInterval(() => this.tick(), 1000) dentro de classe funciona sem bind.
Métodos passados como callback perdem this:
class C {
constructor() { this.n = 1; }
m() { console.log(this.n); }
}
const c = new C();
const f = c.m;
f(); // TypeError: this is undefined (strict) ou this.n é window.n
Solução: bind(c) ou usar arrow.
JS é prototype-based, não class-based. Classes ES6 são sintaxe sobre prototypes.
Cada objeto tem um [[Prototype]] interno (acessível via Object.getPrototypeOf(obj) ou legacy obj.__proto__). Quando você acessa obj.foo:
obj mesmo.[[Prototype]].null.function Animal(name) { this.name = name; }
Animal.prototype.speak = function() { return this.name; };
const dog = new Animal('Rex');
dog.speak(); // procura speak em dog → não acha → procura em Animal.prototype → acha
new Constructor() em 4 passos:
[[Prototype]] = Constructor.prototype.Constructor.call(novoObj, ...args).Class ES6 desugaring:
class Dog extends Animal {
bark() { return 'woof'; }
}
// equivale a:
function Dog(...args) { Animal.call(this, ...args); }
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() { return 'woof'; };
instanceof anda na chain procurando Class.prototype.
Object.create(proto) cria objeto com [[Prototype]] específico, útil pra criar inheritance "manual".
JavaScript é single-threaded mas assíncrono. Como?
Conceitos:
setTimeout, setInterval, setImmediate (Node), I/O callback vira task.Promise.then/catch/finally, queueMicrotask, MutationObserver (browser).Loop:
while (true) {
// 1. Se stack não vazia: executa próxima instrução (sync)
// 2. Se stack vazia:
// a. Drena microtask queue COMPLETAMENTE (pode adicionar mais microtasks; processe todas)
// b. Pega 1 macrotask, executa até esvaziar stack
// c. (browser) Render se necessário
// 3. Se nada a fazer: bloqueia em I/O multiplex (epoll/kqueue/IOCP)
}
Ordem clássica:
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
// Saída: 1, 4, 3, 2
1, 4 síncronos primeiro.3.2.No Node, há fases (timers, pending, idle/prepare, poll, check, close). setImmediate é macrotask na fase check. process.nextTick tem fila própria, antes de microtasks. Evite process.nextTick recursivo (starva o loop).
Promise é objeto com 3 estados: pending, fulfilled, rejected. Transição é única e final.
const p = new Promise((resolve, reject) => {
// executor roda IMEDIATAMENTE (síncrono)
setTimeout(() => resolve(42), 100);
});
p.then(v => console.log(v)); // microtask quando resolver
then(onFulfilled, onRejected) retorna nova Promise representando o resultado da callback.
async/await é sintaxe sobre Promise + generators:
async function fn() {
const x = await promise1;
return x + 1;
}
// Equivale a:
function fn() {
return promise1.then(x => x + 1);
}
Cuidado: await em loop sequencializa.
// SEQUENCIAL, lento se calls são independentes:
for (const url of urls) await fetch(url);
// PARALELO:
await Promise.all(urls.map(u => fetch(u)));
Iterator protocol:
const iter: Iterator<number> = {
i: 0,
next() {
return { value: this.i++, done: this.i > 3 };
},
};
for...of consome iterators. Arrays, Maps, Sets, strings são iterables (têm [Symbol.iterator]).
Generators:
function* counter() {
yield 1;
yield 2;
yield 3;
}
const g = counter();
g.next(); // { value: 1, done: false }
Generators pausam em yield e retomam em next(). Permite lazy sequences, control flow customizado, schedulers.
JS é managed. Você não dá free. Mas leaks acontecem:
el.addEventListener(...) sem removeEventListener.setInterval sem clearInterval.Map que cresce indefinidamente.Ferramentas: Chrome DevTools Memory tab, heap snapshot, allocation timeline.
WeakMap / WeakSet: keys são fracas, não impedem GC. Útil pra cache associada a objetos.
WeakRef (ES2021): referência fraca a objeto, GC pode coletar. Use com FinalizationRegistry pra cleanup.
ES Modules (ESM) desde 2015:
// foo.mjs
export const x = 1;
export default function() {}
// bar.mjs
import { x } from './foo.mjs';
import fn from './foo.mjs';
ESM é estático (imports resolvidos no parse, não em runtime). Permite tree-shaking.
CommonJS (CJS): require/module.exports, é o modelo legacy do Node, dinâmico.
Em Node, package.json "type": "module" faz .js ser ESM. ESM importa CJS, mas CJS importa ESM só com await import() (dinâmico).
Pra passar o Portão Conceitual, sem consultar:
console.log(1);
setTimeout(() => console.log(2));
Promise.resolve().then(() => console.log(3));
console.log(4);
var em loop e listar 3 soluções.[1,2,3] + [4,5,6] passo a passo (ToPrimitive → toString → concat).Dog extends Animal extends Object.this correspondente.await é desugarado pra .then.Map.Implementar MyPromise (Promise A+ compliant) do zero.
Você vai construir uma reimplementação de Promise que passa o test suite oficial Promises/A+ (promisesaplus.com).
MyPromise<T> com:
new MyPromise((resolve, reject) => ...).pending → fulfilled ou pending → rejected. Transição única, irreversível.then(onFulfilled?, onRejected?): retorna nova MyPromise. Se callback retorna valor → fulfill; retorna promise → unwrap; throw → reject.catch(onRejected): shorthand.finally(onFinally).then rodam em microtask (use queueMicrotask em Node ou MutationObserver no browser; não use setTimeout).MyPromise.resolve(value)MyPromise.reject(reason)MyPromise.all(iterable), todos resolvem ou primeiro reject.MyPromise.allSettled(iterable), espera todos, retorna array de {status, value/reason}.MyPromise.race(iterable), primeiro a settle (resolve ou reject).MyPromise.any(iterable), primeiro a resolve, ou AggregateError se todos reject.promises-aplus-tests com adapter (instruções em github.com/promises-aplus/promises-tests). Todos os testes têm que passar.strict: true).vitest + promises-aplus-tests pra rodar suite).setTimeout(0) pra microtask).[[Resolve]] (a parte mais sutil, quando callback retorna outra Promise/thenable, precisa unwrap recursivamente sem loop infinito)..then em chain.queueMicrotask e não setTimeout(0).AbortController / AbortSignal integration (cancelamento).for await ... of).epoll/kqueue/IOCP via libuv. Microtasks rodam em user space; macrotasks são frequentemente acionadas por syscalls completas.--inspect-brk + Chrome DevTools: debugger.--trace-gc: log de GC.--prof + --prof-process: profiling V8.src/builtins/, src/objects/.lib/internal/.Encerramento: 01-07 é o módulo que separa quem escreve JS de quem entende JS. Depois dele, frameworks deixam de ser caixas mágicas. Você passa a ler código de libs, debug em DevTools, e otimizar V8 com base em sinais reais.
Destrava
01-07 é prereq dos seguintes módulos: