🐵

Primitives

Javascript é uma linguagem vagamente e dinamicamente tipada, o que significa que as variáveis não estão diretamente associadas a um tipo específico, e qualquer variável pode ser atribuída e re-atribuída com valores de qualquer tipo.
let foo = 42; // foo é um número foo = 'bar'; // foo agora é uma string foo = true; // foo agora é um boolean

Introdução

O conjunto de tipos na linguagem consistem em primitivos e objetos:
  • Primitivos
    • Boolean
    • Null
    • Undefined
    • Number - floating point 64-bit. Não existem inteiros no Javascript
    • BigInt
    • String
    • Symbol
  • Objetos
Objetos são compostos por um conjunto de propriedades, uma propriedade pode referenciar um outro objeto, um ou primitivo.
Tipos primitivos são apenas valores, eles não tem propriedades. Primitivo é um dado imutável representado no nível mais baixo da linguagem.
Em Javascript, tudo o que não é um primitivo, é um objeto, e isso inclui funções e arrays.
const array = [] typeof array; // object array instanceOf Object; // true function fn() {} // O código abaixo retorna function pois segundo a especifição ECMAScript, objetos que implementam [[Call]] são considerados funções // https://262.ecma-international.org/5.1/#sec-11.4.3 typeof fn; // function fn instanceOf Object; // true
Além disso, os tipos primitivos são armazenados como o próprio valor, diferente dos objetos, que são armazenados como uma referência
"string" === "string"; // true 1 === 1; // true {} === {}; // false [] === []; /// false

Funções

Uma função é um objeto com algumas propriedades especiais, como constructor e call. E como qualquer outro objeto, podemos adicionar novas propriedades.
const fn = function (var) {} fn.name; // fn fn.length; // 1 fn.foo = "bar"; fn.foo; // "bar"
Isso caracteriza a linguagem com funções de primeira classe, pois podem ser passadas como argumentos à outras funções, como qualquer outro objeto.

Funções construtoras

Uma função construtora é como qualquer outra. Uma função é usada como construtora quando invocada usando new .
const Fn = function() {} const foo = new Fn() foo; // {} foo instanceOf Fn; // true foo instanceOf Object; // true
Uma função construtora sempre retorna um objeto, e é possível utilizar this dentro da função para atribuir propriedades a esse objeto.
const Fn = function() { this.foo = "bar"; } const r = new Fn(); r; // { foo: "bar" } r instanceOf Fn; // true r instanceOf Object; // true
Executar uma função Fn construtora sem o new irá ter o mesmo efeito de executar uma função normal. O this dentro da função irá corresponder ao contexto de execução, então, se executarmos Fn() no nível mais alto do script, o this irá modificar o objeto window

Mas se os tipos primitivos não tem propriedades, por que "abc".length retorna um valor?

Isso acontece pois o Javascript consegue fazer a coerção (conversão) entre primitivos e objetos. Nesse caso, quando tentamos acessar uma propriedade ou método de um primitivo, uma conversão será efetuada para um objeto temporário (o construtor do primitivo), que então acessa a propriedade/método, sem afetar o original. Esse objeto posteriormente é removido pelo garbage collector. Esse processo é chamado de autoboxing.
const a = new String('123') typeof a // object a; // 123 const b = '123' b.length; // 3 typeof b // string b; // 123
No exemplo acima, quando acessamos lenght , a variável b é encapsulada pelo objeto String que é descartado em seguida. Isso ocorre sem afetar a variável b , que continua sendo uma string primitiva.
Então, chegamos a conclusão de que valores primitivos tem acesso à todas as propriedades (e métodos) definidas por seus respectivos construtores.

Esses objetos também podem ser convertidos para valores?

Sim, esses objetos apenas encapsulam os valores primitivos e geralmente sofrem coerção quando necessário.
const num1 = new Number(1) const num3 = num1 + 2 num3; // 3 typeof num1; // object typeof num3; // number
Com exceção do objeto Boolean, que é computado como `true` mesmo que seu valor seja false , null ou undefined
const a = new Boolean(false) !!a; // true

A coerção permite atribuir valores à primitivos?

Não
const primitive = "string"; primitive.name = "primitive"; primitive.name; // undefined
Quando o Javascript detecta uma tentativa de atribuir uma propriedade a um primitivo, é feita a coerção do primitivo para um objeto. Porém, esse objeto não tem referências e é imediatamente removido pelo garbage collector.
const primitive = "any-string"; primitive.name = "primitive"; // Um novo objeto é criado para atribuir a propriedade (new String("any-stirng")).name = "primitive"; primitive.name; // Um outro objeto é criado para obter a propriedade (new String("any-stirng")).name; // undefined
Da mesma forma, também não conseguimos modificar as propriedades dos objetos primitivos, já que são imutáveis
const primitive = new String('123') primitive.length = 10; // Erro no strict mode primitive.length; // 3 (e não 10)
Porém, não é possível atribuir uma propriedade aos tipos null e undefined , já que eles não tem um objeto encapsulador.

Referências