# Como o JavaScript é definido: do ECMAScript ao Array.prototype.map https://rafaself.pages.dev/pt/posts/how-javascript-is-defined-from-ecmascript-to-arrayprototypemap-5971/ Usamos JavaScript todos os dias com uma sintaxe como esta: ```js const numbers = [1, 2, 3]; const doubled = numbers.map(number => number * 2); ``` Sabemos o que `map()` faz, mas há uma pergunta mais interessante por trás disso: **Quem decidiu que `map()` deveria se comportar assim?** Por que ele pula posições vazias do array? Por que seu callback recebe o valor, o índice e o array? Por que `Array.prototype.map` pode até ser usado com objetos que não são arrays? As respostas não são específicas do Chrome, do Firefox ou do Node.js. Elas vêm da definição formal da linguagem JavaScript. Para entender isso, precisamos voltar um pouco. ## Do JavaScript ao ECMAScript O JavaScript foi criado por Brendan Eich na Netscape nos anos 1990 e apareceu pela primeira vez no Netscape Navigator. Depois, a Microsoft apresentou sua própria implementação relacionada, chamada JScript. Isso criou um problema importante. Se empresas diferentes pudessem decidir de forma independente como a linguagem funcionava, o código escrito para um navegador poderia se comportar de outra forma em outro. Era necessária uma definição comum da linguagem. O trabalho de padronização começou na **Ecma International** em novembro de 1996, e a primeira edição do padrão ECMAScript foi adotada em junho de 1997. A Ecma International é uma organização de padronização. Nela, diferentes comitês técnicos são responsáveis por tecnologias distintas. O comitê responsável pela linguagem padronizada do JavaScript é o **TC39**, ou **Comitê Técnico 39**. Assim, podemos começar a estabelecer a relação: ```text Ecma International → TC39 → ECMAScript specification ``` O TC39 discute e desenvolve mudanças na linguagem. Novos recursos passam por um processo de propostas antes de integrar o padrão. Mas onde entra o **ECMA-262**? ## ECMA-262, ECMAScript e JavaScript **ECMA-262** é o número do padrão Ecma que define a linguagem ECMAScript. Seu nome é: > ECMAScript Language Specification Portanto, esses termos descrevem coisas diferentes: | Termo | O que é | |---|---| | **Ecma International** | Organização de padronização | | **TC39** | Comitê técnico responsável pelo ECMAScript | | **ECMA-262** | Documento do padrão que define o ECMAScript | | **ECMAScript** | Especificação da linguagem de programação definida pelo ECMA-262 | | **JavaScript** | Linguagem de programação baseada na especificação ECMAScript e que a implementa | O ECMA-262 é publicado periodicamente em novas edições. Por exemplo, o padrão publicado atualmente é o **ECMA-262, 17ª edição**, que define o **ECMAScript 2026**. | Termo | Significado | |---|---| | **ECMA-262** | Identificador do padrão | | **17ª edição** | Edição do padrão ECMA-262 | | **ECMAScript 2026** | Versão do ECMAScript definida por essa edição | A 17ª edição foi publicada em junho de 2026 e define o ECMAScript 2026. O número `262`, portanto, **não é uma versão do JavaScript**. Da mesma forma, `ECMAScript 2026` não significa “versão 2026” no sentido tradicional de versões de software. O ECMAScript segue um modelo de lançamento anual há vários anos. Também existe uma **especificação viva** mantida pelo TC39. Depois da publicação do recorte anual, propostas concluídas e mudanças na especificação continuam sendo incorporadas ao documento vivo. Em outubro de 2026, esse documento já se chama **ECMAScript 2027**. Isso nos dá uma visão mais ampla: ![Diagrama horizontal da padronização e implementação do JavaScript: Ecma International → TC39 → ECMA-262 → linguagem ECMAScript → motores JavaScript → código. Cada etapa aparece em um retângulo conectado por setas sobre um fundo escuro.](image-0fc7aa079d343711.png) Agora podemos voltar ao `map()`. --- ## De onde vem o `map()`? Considere: ```js const numbers = [10, 20, 30]; numbers.map(number => number * 2); ``` Pode parecer que o próprio `numbers` contém uma função `map`. Não contém. ```js Object.hasOwn(numbers, "map"); // false ``` Os arrays herdam esse método pela cadeia de protótipos: ```text numbers → Array.prototype → Object.prototype → null ``` E: ```js numbers.map === Array.prototype.map; // true ``` Portanto, quando escrevemos: ```js numbers.map(callback); ``` O JavaScript encontra `map` em `Array.prototype` e o chama usando `numbers` como valor de `this`. Conceitualmente: ```js Array.prototype.map.call(numbers, callback); ``` É aqui que a definição de `Array.prototype.map` no ECMAScript se torna relevante. ## Lendo a especificação A especificação descreve `map()` aproximadamente assim: ```text 1. Let O be ? ToObject(this value). 2. Let len be ? LengthOfArrayLike(O). 3. If IsCallable(callback) is false, throw a TypeError. 4. Let A be ? ArraySpeciesCreate(O, len). 5. Let k be 0. 6. Repeat while k < len: a. Let Pk be ! ToString(𝔽(k)). b. Let kPresent be ? HasProperty(O, Pk). c. If kPresent is true: i. Let kValue be ? Get(O, Pk). ii. Let mappedValue be ? Call(callback, thisArg, « kValue, 𝔽(k), O »). iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). d. Set k to k + 1. 7. Return A. ``` > A definição está na [ECMAScript® 2026 Language Specification](https://tc39.es/ecma262/pr/3782/multipage/indexed-collections.html#sec-array.prototype.map). Isso não é código-fonte JavaScript. Operações como `ToObject`, `HasProperty`, `Get` e `Call` são chamadas de **operações abstratas**. São ferramentas que a especificação usa para descrever formalmente como o JavaScript deve se comportar. Vamos traduzir esse algoritmo aos poucos. ## Primeiro, identificar o que está sendo mapeado O algoritmo começa com: ```text Let O be ? ToObject(this value). ``` Em: ```js numbers.map(callback); ``` `this` é `numbers`. Então, no nosso exemplo: ```text O = numbers ``` A especificação o chama deliberadamente de `O`, representando um objeto, em vez de algo como `array`. Isso acontece porque `map()` é mais genérico do que parece. Ele também pode trabalhar com alguns objetos que não são arrays. Em seguida: ```text Let len be ? LengthOfArrayLike(O). ``` Para: ```js const numbers = [10, 20, 30]; ``` Temos: ```text len = 3 ``` O algoritmo agora sabe que precisa considerar as posições `0`, `1` e `2`. Depois, verifica se o callback pode ser chamado e cria o array que conterá o resultado. Podemos aproximar esse início com: ```js const O = Object(this); const len = O.length; if (typeof callback !== "function") { throw new TypeError(); } const A = new Array(len); ``` A especificação real é mais precisa, mas esse é um modelo mental útil. ## De um índice a uma propriedade Agora começa a parte interessante. O algoritmo inicializa: ```text k = 0 ``` E repete enquanto: ```text k < len ``` Mas ele não acessa simplesmente: ```js O[k]; ``` Antes, a especificação cria algo chamado `Pk`: ```text Let Pk be ! ToString(𝔽(k)). ``` Se: ```text k = 1 ``` Então, conceitualmente: ```text k ↓ 1 ToString(...) ↓ "1" Pk = "1" ``` Por que converter o índice em uma string? Porque um Array também é um Object. Considere: ```js const arr = ["A", "B", "C"]; ``` No nível da linguagem, podemos pensar nas propriedades indexadas assim: ```text "0" → "A" "1" → "B" "2" → "C" ``` É por isso que: ```js arr[1] === arr["1"]; // true ``` As chaves de propriedades comuns do JavaScript são strings ou símbolos. A especificação transforma a posição numérica usada pelo algoritmo na chave de propriedade usada para inspecionar o objeto. Isso **não** significa que um motor JavaScript precise armazenar fisicamente `"1"` como string em algum lugar da memória. A especificação define o comportamento. A implementação pode otimizá-lo internamente. ## Essa propriedade realmente existe? Depois de obter `Pk`, o algoritmo pergunta: ```text Let kPresent be ? HasProperty(O, Pk). ``` Uma aproximação útil é: ```js const kPresent = Pk in O; ``` Essa distinção importa porque arrays JavaScript podem conter **lacunas**. Compare: ```js const a = [10, undefined, 30]; const b = [10, , 30]; ``` Ambos produzem: ```js a[1]; // undefined b[1]; // undefined ``` Mas estruturalmente são diferentes: ```js 1 in a; // true 1 in b; // false ``` Em `a`, a propriedade existe e seu valor é `undefined`. Em `b`, a propriedade não existe. Por isso, `map()` não pode simplesmente recuperar o valor e verificar se ele é `undefined`. Primeiro, ele pergunta: ```text Does property "1" exist? ``` Somente se a resposta for `true` ele chama o callback. Isso explica este comportamento: ```js [10, , 30].map(value => { console.log(value); }); ``` Saída: ```text 10 30 ``` O callback nunca é executado para a propriedade ausente. ## `HasProperty` e a cadeia de protótipos Há outro detalhe sutil. `HasProperty` não inspeciona apenas as propriedades que pertencem diretamente ao array. Ele também considera a cadeia de protótipos. Isso o aproxima mais de: ```js "1" in arr ``` Do que de: ```js Object.hasOwn(arr, "1") ``` Por exemplo: ```js const arr = new Array(3); Array.prototype[1] = 100; ``` O próprio array ainda não possui `"1"`: ```js Object.hasOwn(arr, "1"); // false ``` Mas: ```js "1" in arr; // true ``` Porque o JavaScript pode encontrá-lo aqui: ```text arr │ │ "1"? no ▼ Array.prototype │ │ "1"? yes └── 100 ``` Portanto: ```js arr[1]; // 100 ``` E como `map()` usa `HasProperty`, isso também o afeta: ```js arr.map(value => { console.log(value); }); ``` O callback recebe `100`. Este exemplo é propositalmente incomum, e modificar protótipos nativos assim deve ser evitado em geral. Mas ele mostra como `map()` interage de perto com o modelo de objetos do JavaScript. ## Lendo e mapeando o valor Depois de confirmar que a propriedade existe, a especificação executa: ```text Get(O, Pk) ``` Para: ```js const numbers = [10, 20, 30]; ``` Quando: ```text Pk = "1" ``` Temos: ```text Get(numbers, "1") → 20 ``` Esse valor se torna `kValue`. A especificação então chama: ```text Call(callback, thisArg, « kValue, 𝔽(k), O ») ``` O que pode ser entendido aproximadamente como: ```js callback.call( thisArg, kValue, k, O ); ``` Isso também explica de onde vêm os argumentos conhecidos do callback: ```js numbers.map((value, index, array) => { // ... }); ``` Eles correspondem diretamente a: ```text kValue → value k → index O → array ``` Se nosso callback for: ```js value => value * 2 ``` Então: ```text kValue = 20 callback(20, 1, numbers) ↓ 40 ``` A especificação chama esse resultado de: ```text mappedValue ``` Por fim, cria a propriedade correspondente no array de resultado: ```text CreateDataPropertyOrThrow(A, Pk, mappedValue) ``` Que podemos imaginar aproximadamente como: ```js A[Pk] = mappedValue; ``` Depois, `k` é incrementado e o processo se repete. ## Juntando as peças Uma versão simplificada do que acabamos de ler seria: ```js function simplifiedMap(callback, thisArg) { const O = Object(this); const len = O.length; if (typeof callback !== "function") { throw new TypeError(); } const A = new Array(len); for (let k = 0; k < len; k++) { const Pk = String(k); if (Pk in O) { const kValue = O[Pk]; const mappedValue = callback.call( thisArg, kValue, k, O ); A[Pk] = mappedValue; } } return A; } ``` Essa **não é a implementação real usada pelo V8 ou por outro motor JavaScript**, nem uma reimplementação completamente fiel de cada operação abstrata do ECMAScript. Mas ela expõe a lógica da especificação. Para: ```js const numbers = [10, 20]; numbers.map(value => value * 2); ``` Podemos acompanhar a primeira iteração: ```text k = 0 ↓ Pk = "0" ↓ HasProperty(numbers, "0") ↓ true ↓ Get(numbers, "0") ↓ 10 ↓ callback(10, 0, numbers) ↓ 20 ↓ result["0"] = 20 ``` Depois: ```text k = 1 ↓ Pk = "1" ↓ HasProperty(numbers, "1") ↓ true ↓ Get(numbers, "1") ↓ 20 ↓ callback(20, 1, numbers) ↓ 40 ↓ result["1"] = 40 ``` E, finalmente: ```js [20, 40] ``` O que parecia um simples laço se apoia em várias partes fundamentais do JavaScript: objetos, chaves de propriedades, busca em protótipos, existência de propriedades, chamadas de funções e criação de arrays. Esse é o valor de ler o ECMA-262. Saímos de saber **como usar JavaScript** para entender **por que ele se comporta dessa forma**.