Um dia desses eu recebi um quebra-gelo no Telegram, com o seguinte:

Number(null);    // 0
null == 0;    // true né?

De cara eu pensei que seria false, mas fiz questão de rodar no console e ver no que dava. Claro que deu false. Mesmo assim, quis entender o motivo de Number(null) retornar 0 e fui procurar na documentação do ECMAScript 6, ou ECMAScript 2015.

O JavaScript, ou ECMAScript, tem um conjunto de operações abastratas que ocorrem por baixo dos panos. Dentre estas operações, temos as conversões de tipos (Types Conversions), que é executada sempre que necessário - que é justamente o caso do Number(null).

Existem várias operações abstratas de conversão de tipos em JS, mas vou abordar apenas as mais comuns.

ToPrimitive

Praticamente tudo em JS é tratado como um objeto, então a conversão ToPrimitive transforma o input para o seu devido tipo primitivo, isto é, retorna o valor sem ser um objeto. Talvez seja um pouco óbvio e muito comum, mas é interessante ver no console. Primeiro vejamos os tipos primitivos:

// Tipos primitivos
String('foo');      // 'foo'
Number(2016);       // 2016
Boolean(true);      // true

Agora, já que temos objetos pra quase tudo em JS, veja o retorno ao rodar no console do Chromium/Chrome:

// Tratando como objetos
new String('foo');
// String {0: "f", 1: "o", 2: "o", length: 3, [[PrimitiveValue]]: "foo"}

new Number(2016);
// Number {[[PrimitiveValue]]: 2016}

new Boolean(false);
// Boolean {[[PrimitiveValue]]: false}

Veja que os objetos sempre guardam o valor primitivo, que é retornado por baixo dos panos quando precisamos utilizar o valor para alguma outra operação, como por exemplo:

var ano = new Number(2016);
ano + 1; // 2017

var str = new String('foo');
str.concat(' bar'); // 'foo bar'

Para converter valores do tipo Object, é feita uma análise de qual o valor primitivo do objeto, por exemplo:

// `Object(2010)` retorna o valor primitivo do objeto `Number`,
// nesse caso, 2010
Object(2010) + 6;   // 2016

// `Object('foo')` retorna o valor primitivo do objeto `String`,
// nesse caso, 'foo'
Object("foo").concat(' bar');   // 'foo bar'

// `Object(true)` retorna o valor primitivo do objeto `Boolean`,
// nesse caso, true
Object(true) && false;  // false

ToNumber

A operação abstrata ToNumber transforma a entrada em um tipo numérico, e é aqui que entramos naquele exemplo do Number(null).

A conversão para valores numéricos funciona basicamente com as seguintes regras:

// Estas regras estão definidas no ECMAScript
Number(undefined);  // NaN
Number(null);       // +0
Number(true);       // 1
Number(false);      // +0

Então, por regra, é por isso que Number(null) retorna 0, e isso não siginifica que null == 0, já que são valores primitivos diferentes.

Mas e quanto a conversão de string para number, Number("2016")?

Para fazer a conversão de uma string, o ToNumber tenta interpretar a string na codificação UTF-16 e caso não consiga, retorna NaN, assim:

Number("2016");       // 2016
Number("20.16");      // 20.16
Number("-0");         // -0
Number("+Infinity");  // +Infinity
Number("++Infinity"); // NaN
Number("201 6");      // NaN
Number("foo");        // NaN

// Para objetos, o retorno é correspondente ao
// valor primitivo do tipo do objeto
Number(Object(2016))  // 2016
Number(Object("21"))  // 21
Number(Object("foo")) // NaN

ToBoolean

A operação abstrata ToBoolean transforma a entrada em um tipo booleano, que assim como o ToNumber, segue algumas regras. Vamos lá:

!!undefined;        // false
!!null;             // false
!!Number(+0);       // false
!!Number(-0);       // false
!!Number(NaN);      // false

// Qualquer outro valor numérico retorna true
!!Number(21);       // true

// String retorna `false` se estiver vazia,
// caso contrário, retorna `true`
!!String("");       // false
!!String("foo");    // true

!!Object();         // true

ToString

A operações abstratas ToString tem a função de transformar a entrada em uma string, e assim como as outras operações aqui descritas, também segue as suas regras de conversão, que são:

String(undefined);      // "undefined"
String(null);           // "null"
String(true);           // "true"
String(false);          // "false"

// Para objetos, o retorno é correspondente ao
// valor primitivo do tipo do objeto
String(Object(2016))  // '2016'
String(Object("21"))  // '21'
String(Object(true))  // 'true'
String(Object(true))  // 'true'
String(Object())      // '[object Object]'

Para converter um Number para string, há uma série de considerações a se fazer, vou citar algumas. Tomando como base String(Number(m)):

// 1. Se m for NaN:
String(Number(NaN)); // "NaN"

// 2. Se m for +0 ou −0:
String(Number(-0)); // "0"

// 3. Se m for menor que 0 (zero):
String(Number(-2016)); // "-2016"

// 4. Se m for +Infinity:
String(Number(+Infinity)); // "Infinity"

// 5. Para números muito grandes, muito pequenos,
// ou que tem alguma forma particular para serem
// representados como Number:
    Number(2345678987654321123456);     // 2.3456789876543211e+21
 String(Number(2345678987654321123456)); // "2.3456789876543211e+21"

ToObject

Por último, temos a ToObject, que transforma a entrada em um objeto, quando possível. Até aqui já tivemos a oportinudade de perceber esse tipo de conversão, já que alguns dos exemplos mostraram como ToObject funciona. O que acontece basicamente é que ToObject avalia o tipo primitivo da entrada e retorna um novo objeto daquele tipo, com o valor da entrada. Veja bem:

Object(2016);    // Number {[[PrimitiveValue]]: 2016}
Object('foo');    // String {0: "f", 1: "o", 2: "o", length: 3, [[PrimitiveValue]]: "foo"}
Object(true);    // Boolean {[[PrimitiveValue]]: true}

Enfim, achei a resposta para o Number(null) retornar 0 e deu pra aprender um bocado. Recomendo que você dê uma olhada na documentação do ECMAScript, existem várias outras operações abstratas interessantes.

Como curiosidade, olha só e tente adivinhar qual o resultado do último:

Object(true);                // new Boolean(true)
Object(true) == Boolean(true);        // true
Object(true) == new Boolean(true);    // ??

O próprio Boolean(true) retorna o valor primitivo true, que faz com que o objeto gerado em Object(true) sofra uma conversão para o valor primitivo e assim fazer a igualdade. Já new Boolean(true) retorna um novo objeto, que na comparação == retorna false. Isso porque ao comparar dois objetos em JS, a comparação é para saber se os dois objetos são iguais. Faça o teste: {} == {}.

Bom... é isso. Espero que eu tenha sido claro, mas se você ficou com dúvidas, me mande um tweet, vamos trocar ideia (:

Até a próxima.