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.