10 Perguntas de Entrevista Sobre JavaScript Para Se Aprofundar
10 Perguntas de Entrevista Sobre JavaScript Para Se Aprofundar
Por Jhonatan Gonçalves • Publicado em 12/10/2022
Salvar
Aprofunde-se, e domine, conceitos da linguagem JavaScript, e esteja preparado também para as próximas entrevistas de emprego, ou para ajudar aquele amigo no entendimento da linguagem.

Prepare-se para a sua próxima entrevista de emprego JavaScript, ou apenas se aprofunde em conceitos que muitas vezes sabemos como funcionam, mas não sabemos explicar. Para não ficar um artigo longo demais, a lista foi limitada para 10 perguntas, assuntos. Então novos artigos serão publicados seguindo essa mesma abordagem. Vamos lá.

1. O que é o Escopo de um código?

O escopo é um conceito que se refere ao lugar onde variáveis e funções podem ser acessadas, reconhecidas, de maneira válida. Existem algumas abrangências para escopo, o escopo global, escopo de função, e escopo de bloco por exemplo.

Declarações realizadas no escopo global podem ser acessadas de qualquer parte do código. Segue um exemplo de declaração global:

//global namespace
let globalVariable = 1

class ClasseTeste {
  static show() {
    // variável acessada no escopo de função
    console.log(globalVariable)
  }
}

ClasseTeste.show() // logs "1"

Declarações realizadas no escopo de função podem ser acessadas no nível atual ou em funções aninhadas, porém nunca fora do seu escopo. Para uma melhor exemplificação, segue o exemplo a seguir:

// escopo global
function mostrarNome() {
  // escopo da função

  let userName = "Peter"
  console.log(userName) // logs "Peter"

  // funcao interna (aninhada)
  function mostrarNome2() {
    console.log(userName)
  }
  mostrarNome2() // logs "Peter"
}
mostrarNome()

// fora do escopo - erro ao acessar
console.log(userName) // logs "Uncaught ReferenceError: name is not defined"

Escopo de bloco é basicamente aqueles declarados entre chaves, {}, e declarações let e const só podem ser acessadas internamente. Exemplo:

function mostrarNome() {
  if (true) {
    // escopo de bloco if
    let userName = "Peter"
  }

  // erro ao acessar
  console.log(userName) // Uncaught ReferenceError: userName is not defined
}
mostrarNome()

E por fim, o Scope Chain (ou cadeia de escopo), é o comportamento padrão do JavaScript de procurar a variável no escopo atual, ou no escopo anterior, até chegar ao escopo global. Caso não seja encontrada a variável em nenhum escopo, é levantado o erro “is not defined”.

2. O que é Hoisting?

Hoisting é o comportamento padrão do JavaScript de mover todas as declarações (variáveis e funções) para o topo do escopo atual.

Na fase de compilação, todas as declarações de funções são movidas para o topo e assim podemos referenciá-las, o mesmo ocorre com as variáveis declaradas como var, elas são movidas para o topo e implicitamente é dado o valor undefined.

Note que apenas as declarações de funções e as variáveis declaradas como var podem ser referenciadas antes de suas declarações sem que seja levantado um erro, como ocorre no acesso a variáveis declaradas como let e const, ou expressões de funções.

O código a seguir apresenta dois exemplos com o uso de funções, um funcionando e o outro não.

// Declaração de função (funciona)
isHoisted(); // logs "hoisted teste"

function isHoisted() {
  console.log("hoisted teste");
}

// Expressão de função (não funciona)
isNotHoisted(); // TypeError: isNotHoisted is not a function

var isNotHoisted = function() {
   console.log("not hoisted test");
};

No exemplo anterior, a função isHoisted foi executada com sucesso mesmo sendo chamada antes da sua declaração a nível de código. Isso se dá porque em tempo de execução, a função foi movida para o topo do código, como será exemplificado mais abaixo.

E o código a seguir apresenta dois exemplos com o uso de variáveis, um declarado como var e o outro como let.

// Declaração do tipo var (funciona)
console.log(hoistedVariable) // logs "undefined"
hoistedVariable = 1
var hoistedVariable;
console.log(hoistedVariable) // logs "1"

// Declaração com let (não funciona)
console.log(hoistedVariable) // logs "Uncaught ReferenceError: hoistedVariable is not defined"
hoistedVariable = 1
let hoistedVariable;
console.log(hoistedVariable)

Tornando o exemplo mais visual para uma melhor compreensão, o que ocorre é o remanejamento do código para a seguinte ordem. Demonstrando com o código que de fato funciona.

// Código remanejado
function isHoisted() {
  console.log("hoisted teste");
}

var hoistedVariable; // undefined

// execução

isHoisted(); // logs "hoisted teste"

console.log(hoistedVariable) // logs "undefined"
hoistedVariable = 1
console.log(hoistedVariable) // logs "1"

3. O que é Closure?

Closure é uma função que preserva o estado dos escopos anteriores (externos) dentro de seu escopo interior. Ou seja, ela preserva, por exemplo, o valor de uma variável declarada, mesmo quando a execução da closure ocorre fora do escopo em que a variável está inserida.

Como visto na pergunta O que é Escopo, uma variável que é declarada em determinado escopo, só pode ser acessada em seu próprio escopo, ou nos escopos internos diretamente ligados a ela. Vejamos a seguir, um exemplo de acesso a uma variável fora de seu escopo:

// escopo global
function controlarNome () {
  // escopo da função controlarNome
  let userName = "Peter Parker"
}
controlarNome()
console.log(userName)

Ao executar o código anterior, será levantado um erro no console.log devido a tentativa de acessar uma variável fora de seu escopo.

O código a seguir é um exemplo do uso de closure.

// escopo global
function controlarNome () {
  // escopo da função controlarNome
  let userName = "Peter Parker"
  
  function getName() {
    // escopo da função getName
    return userName
  }
  
  return getName
}
const getName = controlarNome()
console.log(getName())

No exemplo anterior, a função getName preserva o valor da variável userName, e a função controlarNome retorna a função getName como uma closure. Ao executar o getName, recuperamos o valor de name preservado no escopo interno da closure.

Demonstração de um exemplo mais prático. No exemplo a seguir, será preservado valores dinâmicos.

function saudar (message) {
  return function (name) {
    return `${message} ${name}` 
  }
}

const saudarOi = saudar('Oi')
const saudarOla = saudar('Olá')

console.log(saudarOi('Peter')) // Oi Peter
console.log(saudarOla('Peter')) // Olá Peter

A função saudar recebe um argumento message e retorna uma closure que recebe um argumento name. Por sua vez, a closure preserva o valor de message, e retorna uma string com a junção dos valores dos parâmetros.

Por fim, as constantes saudarOi e saudarOla armazenam as closures retornadas da função saudar com o message preservado. Após isso, as closures são executas e suas respectivas mensagens são apresentadas.

4. Qual a diferença entre Undefined e Null?

Ambos são tipos primitivos no JavaScript. Undefined é o valor default (padrão) de uma variável que não foi inicializada com um valor específico, ou o valor default ao acessar um item inexistente de um Objeto. Null é um valor que deve ser explicitamente atribuído, e ele literalmente representa nenhum valor.

Exemplos de undefined

let name;
const obj = {a: 1, b: 2}
const funcTeste = () => {}

console.log(name) // undefined
console.log(obj.c) // undefined
console.log(funcTeste()) // undefined

Como visto no exemplo anterior, uma função que não possui retorno, o seu retorno padrão também é undefined.

Exemplos de null

let name = null;
const obj = {a: 1, b: 2, c: null}
const funcTeste = () => { return null }

console.log(name) // null
console.log(obj.c) // null
console.log(funcTeste()) // null

Em resumo, use null quando desejar, de maneira explicita, indicar que a variável não possui um valor.

Para fins de curiosidade, o tipo de undefined é undefined, e o tipo de null é um objeto. O fato de null ser um objeto foi causado de maneira não intencional, segue link para mais detalhes https://developer.mozilla.org/en-US/docs/Glossary/Null.

console.log(typeof undefined); // undefined
console.log(typeof null) // object

5. Para que serve o “use strinct”?

A utilização do “use strict” faz com que a execução do código seja realizada em modo restrito, realizando assim validações mais abrangentes. Descuidos que normalmente não seriam identificados, com o “use strict” é possível minimizar o surgimento de bugs que poderiam ser levantados mais cedo ou mais tarde.

Exemplos de restrição do Strict Mode

Atribuindo ou acessando variáveis que não foram devidamente declaradas

function saveValue(x){
  "use strict";
  total = x;
  return total;
}

saveValue(9)
// error: Uncaught ReferenceError: total is not defined

Parâmetros com nomes duplicados

"use strict";
function create(name, age, height, name){
}

// error: Uncaught SyntaxError: Duplicate parameter name not allowed in this context

Deletar uma variável não é permitido

"use strict";
let x = 9;
delete x;

Existem muitas outras restrições, caso queira conhecer mais, acesse https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode.

6. O que são Template Literals (Template Strings)?

O Template Literals, adicionado no ES6, é uma maneira prática de declarar strings no JavaScript. Para criar uma string utilizando este recurso, utilize o acento grave, `, chamado também de backtick ou back-quote.

Um exemplo simples de criação de string.

// ES5
const frase1 = 'Utilizando \'template literals\' no seu projeto'

// ES6
const frase2 = `Utilizando 'template literals' no seu projeto`

No ES5, ao utilizar o apóstrofo , caso dentro da string seja também utilizado o apóstrofo, será preciso utilizar o escape \ para não conflitar. Quando utilizado o template literal do ES6, isso não é mais necessário.

// ES5
const fraseLonga1 = 'Criação\n'
  + 'de uma texto longo\n'
  + 'para exemplo.\n';

// ES6
const fraseLonga2 = `Criação 
 de uma texto longo
 para exemplo.`;

Com o ES6, template literals, também é possível criar strings, textos, longos sem a necessidade do operador mais, + .

const name = 'Peter Parker'

// ES5
const saudacao1 = 'Olá, ' + name + '. Tudo bem?'

// ES6
const saudacao2 = `Olá, ${name}. Tudo bem?`

E uma das funcionalidades mais interessantes do Template Literal é a concatenação de valores na string. Na versão ES5 era preciso a utilização do operador de concatenação, + para adicionar um valor a string, já na versão ES6, é possível fazer de uma maneira mais simples e clean, com ${expressao}.

7. Quais as diferenças entre var, let e const?

Diferente de let e const que são block scoped (escopo de bloco), o var é function scoped. Isso é, uma variável declarada com var pode ser acessada no escopo de uma função mesmo que ela tenha sido declarada em um escopo de bloco interno, exemplo, um if.

Segue um exemplo do funcionamento em questão:

function getName() {
  if(true) {
    var personName = 'Peter Parker';
  }
  return personName 
}

console.log(getName())
// log: Peter Parker

Como exemplificado no código anterior, a variável personName foi declarada dentro do if, porém o seu valor foi acessado fora dele.

Agora, façamos o mesmo teste com let e const.

function getName() {
  if(true) {
    let personName = 'Peter Parker';
  }
  return personName 
}

function getName2() {
  if(true) {
    const personName = 'Peter Parker';
  }
  return personName 
}

console.log(getName())
console.log(getName2())

// log: Uncaught ReferenceError: personName is not defined

Na execução do código anterior, será levantado o erro de que a variável não foi definida, visto que elas funcionam como block scoped.

Outra diferença crucial entre elas é que as declarações com var e let podem ter os seus valores alterados no decorrer da execução do código, porém as declaradas com const ocasionará em erro.

var personName1 = 'Peter'
personName1 = 'Peter Parker'
// ok

let personName2 = 'Peter'
personName2 = 'Peter Parker'
// ok

const personName3 = 'Peter'
personName3 = 'Peter Parker'
// error: Uncaught TypeError: Assignment to constant variable

8. Por que funções são chamadas de First-class Objects?

Funções em JavaScript são First-class porque elas podem ser tratadas como uma variável. Ou seja, a função pode ser atribuída a uma variável, pode ser passada como argumento para outra função, e pode ser retorna como valor de uma função.

Exemplos

const showName = function() {
   console.log("Peter Parker");
}
showName();

const getMethod = function () {
  return function () {
    console.log('Peter Parker') 
  }
}

const show = getMethod()
show()
// executa o método principal, e o método retornado

9. O que são Promises?

Basicamente, a Promise (ou promessa), em JavaScript, permite que um determinado código seja executado de maneira assíncrona. O valor a ser retornado, devido ao que se deseja alcançar com a funcionalidade, muitas vezes não está bem definido no momento de sua criação. E dessa maneira, é preciso trabalhar com alguns estados da Promise.

Os estados de uma promise são:

  • Pending (Pendente): Estado inicial, não houve conclusão do processamento.
  • Fulfilled (Realizada): Foi executado com sucesso na operação.
  • Rejected (Rejeitada): Ocorreu um erro, não foi executado como esperado.

O construtor da Promise tem dois parâmetros, resolve e reject. Se a execução do código ocorrer como o esperado, deve ser executado o método resolve, passando por parâmetro, se assim desejar, algum dado a ser manipulado no .then . Caso a execução do código não ocorra como o esperado, executar o método reject, passando se assim desejar, alguma informação do erro a ser manipulado no .catch.

Um exemplo simples:

new Promise((resolve, reject) => {
  const success = true // código assíncrono a ser executado  
  if(success) {
    resolve('success data')
  } else {
    reject('error message')
  }
}).then((data) => {
  console.log(data)
}).catch((error) => {
  console.log(error)
})
console.log('log para teste sync')

// Ordem de execução

// 1. log para teste sync
// 2. 'success data' ou 'error message'

Para fins de exemplo, e para facilitar o entendimento, o código anterior focou apenas em como seria a resolução da Promise caso executada com sucesso ou tenha sido rejeitada. Como dica, a criação da Promise poderia ser facilmente abstraída em uma função a parte para evitar o instanciamento repetitivo.

10. O que é async e await e como ele funciona?

Como vimos no item anterior, a Promise nos permite executar códigos de forma assíncrona. Async e await é um recurso construído em cima das funcionalidades da Promise, funcionalidades estas que nos permite tratar de uma maneira diferente a execução de códigos assíncronos. E além disso, é uma forma mais legível e limpa de escrever, evitando um encadeamento de Promises e Callbacks.

Exemplo da consulta de uma api com a utilização de Promises.

function consultar() {
  fetch("https://www.fishwatch.gov/api/species")
    .then(resp => resp.json())
    .then(data => {
      console.log(data)
    }).catch(error => {
      console.log('Ocorreu um erro', error)
    });
}
consultar()

Agora, utilizaremos o Async/Await para demonstrar a mesma funcionalidade anterior.

async function consultar() {
    try {
      const response = await fetch("https://www.fishwatch.gov/api/species")
      const data = await response.json()
      console.log(data)
    } catch(error) {
      console.log('Ocorreu um erro', error)
    }  
}
consultar()

A palavra-chave async é utilizada na declaração da função, e ela retorna implicitamente uma Promise. Quando utilizado o await, a execução do código é realizada de forma linear, se espera o retorno da expressão a ser executada, e só então passa para a execução da próxima linha, como a execução de um código síncrono normal. O await deve ser utilizado apenas dentro de funções declaradas como async, caso contrário, será levantado um erro.

***

Para o artigo não ficar longo demais e muito maçante, uma série de outras perguntas serão acrescentadas em artigos futuros. Gostou do conteúdo? Inscreva-se em nossa newsletters, e siga-nos no Instagram e Twitter.

2 likes |
Gostou? Adicione aos seus artigos
Próxima leitura
Não sabe por onde começar?

Receba conteúdos em primeira mão e fique por dentro das novidades! Junte-se a nós.

É rápido e prático. Cadastre-se ou inscreva-se em nossa newsletters, e pronto.