23 perguntas difíceis para entrevistas em JavaScript

Deseja se preparar para uma entrevista em JavaScript e está procurando perguntas para praticar? Se sim - considere que sua pesquisa terminou. O autor do material, cuja tradução publicamos hoje, diz que ele coletou mais de duas dúzias de perguntas sobre JavaScript, destinadas a quem deseja passar de júnior para sênior, para aqueles que procuram passar com êxito por uma entrevista no campo de desenvolvimento front-end e obter uma oferta interessante de o empregador.


imagem

1. Explique os recursos de validação de igualdade de JavaScript


Dificuldade: *


JavaScript tem dois operadores para verificar a igualdade de valores. O primeiro é o chamado operador de igualdade estrita. O segundo é o operador de igualdade não restrito, que pode ser usado para converter os tipos de quantidades verificadas.

  • O operador de igualdade estrita ( ===) verifica os valores de igualdade sem realizar conversões de tipo.
  • O operador de igualdade não estrito ( ==) verifica os valores de igualdade, convertendo-os em um tipo comum.

var a = "42";
var b = 42;

a == b;         // true
a === b;        // false

Aqui estão algumas diretrizes para o uso de vários verificadores de igualdade no JavaScript:

  • Se algum dos valores comparados puder ser um valor trueou false- tente evitar o operador ==. Use um operador ===.
  • Use o operador ===em caso de que se você está trabalhando com os seguintes valores: 0, «»ou [](array vazio).
  • Em todos os outros casos, você pode usar o operador com segurança ==. Além disso, isso não é apenas seguro, mas também ajuda a simplificar o código e melhorar sua legibilidade.

Origem

2. Dê exemplos de conversão para um tipo lógico de valores que não estão relacionados a esse tipo


Dificuldade: ***


A essência dessa pergunta é descobrir quais valores, no caso de convertê-los em um tipo lógico, se transformam falsee quais - em true.

Aqui está uma lista de valores que podem ser chamados de "falsidade". Ao converter para um tipo lógico, eles se transformam em um valor false:

  • «» (linha vazia).
  • 0, -0, NaN(Não um número).
  • null, undefined.

"Falso" é um significado lógico false.

Qualquer valor que não esteja incluído nesta lista, quando convertido em um tipo lógico, se transforma em true(esses valores são chamados de "true" - na verdade). Por exemplo:

  • «hello».
  • 42.
  • [ ], [ 1, «2», 3 ](matrizes).
  • { }, { a: 42 }(objetos).
  • function foo() { .. } (funções).

"Verdadeiro" também é um significado lógico true.

Origem

3. O que é IIFE?


Dificuldade: ***


IIFE (expressão da função chamada imediatamente) é uma expressão funcional chamada imediatamente. Essa expressão é executada imediatamente após a criação.

(function IIFE(){
    console.log( "Hello!" );
})();
// "Hello!"

Esse padrão é frequentemente usado para evitar a poluição do espaço para nome global. O fato é que as variáveis ​​declaradas no IIFE (como em qualquer outra função comum) são invisíveis fora dessa função.

Origem

4. Quando devo usar as funções de seta que apareceram no ES6?


Dificuldade: ***


Aqui estão regras simples para usar as várias maneiras de declarar funções que sigo ao desenvolver código para ambientes que suportam ES6 e padrões mais recentes:

  • Use a palavra-chave functionno escopo global e para propriedades Object.prototype.
  • Use a palavra-chave functionpara construtores de objetos.
  • Em outros casos, use as funções de seta.

Como você pode ver, as funções de seta são recomendadas para serem usadas em quase todos os lugares. Existem várias razões para esse estado de coisas:

  • Trabalho conveniente com contexto. As funções de seta usam o valor do thiscontexto circundante sem ter o seu próprio this. Se essas funções forem usadas seqüencialmente, sem o uso de funções comuns em construções complexas, isso garante um trabalho seguro com o contexto.
  • Compacidade. O código da função da seta é mais fácil de inserir e mais fácil de ler. Talvez essa vantagem das funções de seta em relação às comuns pareça controversa e dependendo do ponto de vista de cada desenvolvedor específico.
  • Clareza do código. Se quase todo o código for representado por funções de seta, qualquer função comum será distinguida nesse código, criando seu próprio contexto. Usando as funções de seta, o programador cria um código mais compreensível no qual é mais fácil trabalhar do que no código sem as funções de seta this.

Origem

5. Qual é a diferença entre as classes ES6 e os construtores de funções?


Dificuldade: ***


Vejamos alguns exemplos primeiro.

Função construtora:

function Person(name) {
  this.name = name;
}

Classe ES6:

class Person {
  constructor(name) {
    this.name = name;
  }
}

Quando se trata de criar objetos simples, os construtores e classes usados ​​para esse fim são muito semelhantes.

A principal diferença entre construtores e classes aparece ao usar herança. Se precisarmos criar uma classe Studentque é uma subclasse da classe Persone adicionar um campo a essa nova classe studentId, é assim que o código no qual os construtores são usados ​​e o código no qual as classes são usadas.

Função construtora:

function Student(name, studentId) {
  //      ,   .
  Person.call(this, name);

  //    .
  this.studentId = studentId;
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Classe ES6:

class Student extends Person {
  constructor(name, studentId) {
    super(name);
    this.studentId = studentId;
  }
}

Origem

6. Conte-nos sobre o método Function.prototype.bind ().


Dificuldade: ***


Para citar o MDN: “O método bind()cria uma nova função que, quando chamada, define o thisvalor fornecido como o contexto de execução . O conjunto de argumentos também é passado para o método, que será definido antes dos argumentos passados ​​para a função vinculada quando for chamado. ”

Eu acredito nesse método. bind()especialmente útil para vincular valores thisem métodos de classe que precisam ser passados ​​para outras funções. Essa técnica é frequentemente usada nos componentes React.

Origem 

7. Para que as funções anônimas são comumente usadas?


Dificuldade: ***


Funções anônimas são usadas para criar construções IIFE, as variáveis ​​declaradas nas quais não poluem o escopo global.

(function() {
  // - .
})();

Funções anônimas são usadas como funções de retorno de chamada, que são usadas apenas em um local do programa. O código parecerá mais auto-suficiente e legível se o retorno de chamada for anunciado exatamente no local em que é usado. Isso elimina a necessidade de examinar o código em busca do corpo da função.

setTimeout(function() {
  console.log('Hello world!');
}, 1000);

As funções anônimas são convenientemente usadas em construções específicas para o estilo de programação funcional ou ao trabalhar com bibliotecas como o Lodash (esse caso de uso é semelhante ao uso como retornos de chamada).

const arr = [1, 2, 3];
const double = arr.map(function(el) {
  return el * 2;
});
console.log(double); // [2, 4, 6]

Origem

8. Qual é a diferença entre o método Object.freeze () e a palavra-chave const?


Dificuldade: ***


Palavra const- chave e método Object.freeze()são coisas completamente diferentes.

A palavra-chave constse aplica a ligações (a "variáveis"). Ele cria uma ligação imutável, ou seja, é constimpossível vincular algo novo a uma variável (constante) declarada usando uma palavra-chave . Uma constante não pode ser atribuída a um novo valor.

const person = {
    name: "Leonardo"
};
let animal = {
    species: "snake"
};
person = animal; // Uncaught TypeError: Assignment to constant variable.

O método Object.freeze()funciona com valores. Ou melhor, com valores de objeto. Torna o objeto imutável, o que protege contra alterações no valor das propriedades desse objeto.

let person = {
    name: "Leonardo"
};
Object.freeze(person);
person.name = "Lima"; // Uncaught TypeError: Cannot assign to read only property 'name' of object
console.log(person);

Observe que a mensagem de erro é exibida no modo estrito. No modo normal, a operação de alterar a propriedade de um objeto "congelado" simplesmente não funciona.

Origem

9. O que é um "gerador"?


Dificuldade: ***


Geradores são funções das quais você pode "sair" e nas quais pode "entrar", conforme necessário. Seu contexto (ligações variáveis) é mantido entre as sessões de "entrada" nelas. Os geradores são declarados usando uma palavra-chave function*. Essa função, quando é chamada pela primeira vez, não executa o código, retornando um objeto especial, um gerador, que permite controlar sua execução. Para obter o valor próximo emitido pelo gerador, você precisa chamar o seu método next(). Por esse motivo, o código da função é executado até encontrar uma palavra-chave yieldque retorna um valor.

A função do gerador pode ser chamada quantas vezes você desejar. Cada vez que um novo gerador retornará. Mas cada gerador pode ser ignorado apenas uma vez.

function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
    let iterationCount = 0;
    for (let i = start; i < end; i += step) {
        iterationCount++;
        yield i;
    }
    return iterationCount;
}

Origem

10. Quando os geradores devem ser usados?


Dificuldade: ***


Em poucas palavras, para descrever os principais recursos úteis dos geradores, eles são os seguintes:

  • O código no qual o gerador é usado determina o momento em que o próximo valor é recebido. O gerador é responsável apenas pelo retorno dos valores, é controlado a partir do exterior.
  • Existem geradores assíncronos. Eles permitem que você trabalhe com fluxos de dados assíncronos.

O principal nos geradores é que você pode obter o próximo valor retornado pelo gerador somente quando necessário no código que usa o gerador. Os geradores não retornam tudo de uma vez. Em algumas situações, esse recurso pode ser muito conveniente.

Origem

11. O que é "aumentar variáveis"?


Dificuldade: ****


A essência do conceito de "aumentar variáveis" é que os anúncios "sobem" para o topo do escopo atual. Como resultado, a variável pode ser usada antes de sua declaração. Somente declarações de variáveis ​​são geradas, mas não seu código de inicialização. Observe que o comportamento das variáveis ​​declaradas usando a palavra var- chave é diferente do comportamento das variáveis ​​e constantes declaradas usando lete const.

Origem

12. Qual será o código a seguir?


Dificuldade: ****


var output = (function(x) {
  delete x;
  return x;
})(0);

console.log(output);

Este código será exibido 0. O operador é deleteusado para excluir as propriedades dos objetos. E x- isso não é uma propriedade de objeto - é uma variável local. O operador deletenão afeta variáveis ​​locais.

Origem

13. Qual será o código a seguir?


Dificuldade: ****


var Employee = {
  company: 'xyz'
}
var emp1 = Object.create(Employee);
delete emp1.company
console.log(emp1.company);

Este código será exibido xyz. Uma propriedade companynão é uma propriedade de um objeto emp1, mas uma propriedade de seu protótipo. O operador deletenão exclui as propriedades do protótipo dos objetos. Um objeto emp1não possui sua própria propriedade company. Você pode verificar isso:

console.log(emp1.hasOwnProperty('company')); // false

Se ainda precisarmos remover essa propriedade, você poderá fazer isso entrando em contato diretamente com o objeto Employee( delete Employee.company) ou com o protótipo do objeto emp1usando sua propriedade __proto__( delete emp1.__proto__.company).

Origem

14. Conte-nos sobre o padrão de design do protótipo.


Dificuldade: ****


Protótipo é um padrão de design genérico. É usado para criar objetos. Os objetos criados usando-o contêm valores copiados de seu protótipo (do objeto de amostra). Este modelo também é chamado de modelo Propriedades.

Um exemplo do uso do padrão "protótipo" é a inicialização de certos objetos com valores padrão armazenados no banco de dados. Esses valores registrados no protótipo são copiados para novos objetos sem acessar o banco de dados.

Deve-se notar que esse padrão raramente é usado em idiomas clássicos. JavaScript usa um modelo de herança de protótipo. Esse padrão é usado no design de novos objetos e seus protótipos.

Origem

15. O que é uma "zona morta temporária" no ES6?


Dificuldade: ****


O ES6 realizado o levantamento de variáveis e constantes declaradas usando as palavras-chave lete const(isso é feito e o aumento da entidade declarada usando palavras-chave var, classe function). No entanto, o código possui uma zona que se estende da inserção do escopo à declaração de uma variável ou constante. Ao acessar uma variável ou constante nesta zona, um erro será gerado. Esta é a "Zona morta temporal" (TDZ).

//console.log(aLet)  //  ReferenceError

let aLet;
console.log(aLet); // undefined
aLet = 10;
console.log(aLet); // 10

Neste exemplo, o TDZ termina após a declaração aLet, mas não após o aLetvalor ser atribuído .

Origem

16. Você pode descrever a principal diferença entre os métodos de matriz forEach () e map ()? Em que situações você prefere um desses métodos a outro?


Dificuldade: ****


Para entender a diferença entre esses métodos, vamos falar sobre os recursos de cada um deles.

Veja como funciona .forEach():

  • Ele itera sobre os elementos da matriz.
  • Ele executa a função de retorno de chamada transmitida a ele para cada elemento da matriz.
  • Não retorna nada.

const a = [1, 2, 3];
const doubled = a.forEach((num, index) => {
  //  -  num /  index.
});

// doubled = undefined

Aqui está uma breve descrição do método .map():

  • Ele itera sobre os elementos da matriz.
  • Ele converte cada elemento da matriz original em um elemento da nova matriz, chamando a função passada a ele para cada elemento da matriz original.

const a = [1, 2, 3];
const doubled = a.map(num => {
  return num * 2;
});

// doubled = [2, 4, 6]

Como resultado, verifica-se que a principal diferença entre .forEach()e .map()é que ela .map()retorna uma nova matriz. Se você precisar obter o resultado da conversão dos elementos da matriz original sem alterar essa matriz, deverá escolher .map(). Se você apenas precisar iterar sobre os elementos da matriz - poderá usá-lo .forEach().

Origem

17. Qual é a diferença entre uma variável não declarada, uma variável que contém um valor nulo e uma variável não definida? Como verificar uma variável pelo fato de ela não ser declarada, assim como nula e indefinida?


Dificuldade: ****


Uma variável não declarada é criada quando um valor é atribuído a um identificador que não foi declarado anteriormente usando var, letou const. Variáveis ​​não declaradas são declaradas no escopo global, fora do escopo atual. No modo estrito, uma exceção é lançada ao tentar atribuir um valor a uma variável não declarada ReferenceError. O uso de variáveis ​​não declaradas não é recomendado - assim como o uso de variáveis ​​globais não é recomendado. Eles devem ser evitados por todos os meios. A fim de proteger-se contra as consequências da utilização de variáveis não declarado, utilize o bloco try/catch.

function foo() {
  x = 1; //     ReferenceError
}

foo();
console.log(x); // 1

Uma variável que contém undefinedé uma variável declarada à qual não é atribuído um valor. O valor undefinedforma seu próprio tipo de dados. Se a função não retornar nada e o resultado de sua chamada for gravado em uma variável, ela cairá nessa variável undefined. Para organizar uma verificação undefined, você pode usar o operador de igualdade estrita ( ===) ou o operador typeofque retorna uma string undefined. Observe que, ao procurar, undefinedvocê não deve usar o operador de igualdade não estrito ( ==), pois ele considera os valores undefinede iguais null.

var foo;
console.log(foo); // undefined
console.log(foo === undefined); // true
console.log(typeof foo === 'undefined'); // true

console.log(foo == null); // true.        undefined!

function bar() {}
var baz = bar();
console.log(baz); // undefined

Uma variável que contém um valor nulldeve ser definida explicitamente para esse valor. Ele simboliza a ausência de significado e difere de undefined-variable, pois o valor nele foi explicitamente atribuído a ele. Para verificar o valor null, basta usar o operador de igualdade estrita. Para verificar null, como no caso de verificação undefined, não se deve usar o operador de igualdade não estrito, que considera os valores igual nulle igual a undefined.

var foo = null;
console.log(foo === null); // true
console.log(typeof foo === 'object'); // true

console.log(foo == undefined); // true        null!

Eu tento nunca deixar variáveis ​​em um estado não declarado ou em um estado em que elas são declaradas, mas elas não recebem explicitamente nenhum valor. Se não vou escrever um valor em uma variável imediatamente após a declaração, escrevo nela null. Se você usa um linter, ele geralmente relata casos de uso de variáveis ​​não declaradas.

Origem

18. Conte-nos sobre o módulo de design "Módulo aberto"


Dificuldade: *****


O modelo "Revealing Module" é uma variação do modelo "Module". O objetivo de usar esse padrão é oferecer suporte ao encapsulamento e descobrir algumas propriedades e métodos retornados no literal do objeto. Veja como será a implementação direta deste modelo:

var Exposer = (function() {
  var privateVariable = 10;

  var privateMethod = function() {
    console.log('Inside a private method!');
    privateVariable++;
  }

  var methodToExpose = function() {
    console.log('This is a method I want to expose!');
  }

  var otherMethodIWantToExpose = function() {
    privateMethod();
  }

  return {
      first: methodToExpose,
      second: otherMethodIWantToExpose
  };
})();

Exposer.first();        // : This is a method I want to expose!
Exposer.second();       // : Inside a private method!
Exposer.methodToExpose; // undefined

A desvantagem óbvia desse modelo é que você não pode usar métodos privados ao usá-lo.

Origem

19. Qual é a diferença entre os objetos Map e WeakMap?


Dificuldade: *****


Esses objetos se comportam de maneira diferente se uma variável que contém uma referência a um objeto que é a chave de um dos pares chave / valor não está disponível. Aqui está um exemplo:

var map = new Map();
var weakmap = new WeakMap();

(function() {
    var a = {
        x: 12
    };
    var b = {
        y: 12
    };

    map.set(a, 1);
    weakmap.set(b, 2);
})()

Depois que a execução do IIFE for concluída, não teremos mais acesso aos objetos ae b. Portanto, o coletor de lixo remove a chave bde weakmape limpa a memória. Mas o conteúdo mappermanece o mesmo.

Como resultado, verifica-se que os objetos WeakMappermitem que o coletor de lixo se livre dos registros que não são referenciados em variáveis ​​externas. Os objetos maparmazenam pares de chave / valor, independentemente da presença ou ausência de referências de chave externas. O mesmo pode ser dito sobre a implementação da estrutura de dados Mapusando matrizes comuns. As WeakMapreferências de tecla "fracas" são usadas. Eles não interferem na operação do coletor de lixo se não houver outras referências ao objeto usado como chave.

Origem

20. Como os parâmetros são passados ​​para a função JavaScript: por referência ou por valor?


Dificuldade: *****


Os parâmetros são sempre passados ​​por valor, mas as referências aos objetos são gravadas nas variáveis ​​que representam os objetos. Portanto, quando um objeto é transferido para uma função e a propriedade desse objeto é alterada, essa alteração é salva no objeto mesmo quando a função é encerrada. Como resultado, há uma sensação de que os parâmetros na função são passados ​​por referência. Mas se você alterar o valor da variável que representa o objeto, essa alteração não afetará objetos que estão fora da função.

Aqui está um exemplo:

function changeStuff(a, b, c)
{
  a = a * 10;
  b.item = "changed";
  c = {item: "changed"};
}

var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};

changeStuff(num, obj1, obj2);

console.log(num);
console.log(obj1.item);
console.log(obj2.item);

Aqui está o que esse código produzirá:

10
changed
unchanged

Origem

21. Como organizar um “congelamento profundo” de um objeto?


Dificuldade: *****


Para fornecer o "congelamento profundo" de um objeto usando Object.freeze(), você precisa criar uma função recursiva que "congele" as propriedades do objeto, que também são objetos.

Aqui está um exemplo de um "congelamento" comum de um objeto:

let person = {
    name: "Leonardo",
    profession: {
        name: "developer"
    }
};
Object.freeze(person); //   
person.profession.name = "doctor";
console.log(person); // { name: 'Leonardo', profession: { name: 'doctor' } }

Aqui está o "congelamento profundo":

function deepFreeze(object) {
    let propNames = Object.getOwnPropertyNames(object);
    for (let name of propNames) {
        let value = object[name];
        object[name] = value && typeof value === "object" ?
            deepFreeze(value) : value;
    }
    return Object.freeze(object);
}
let person = {
    name: "Leonardo",
    profession: {
        name: "developer"
    }
};
deepFreeze(person);
person.profession.name = "doctor"; // TypeError: Cannot assign to read only property 'name' of object

A mensagem de erro é exibida apenas no modo estrito. No modo normal, o valor não muda sem mensagens de erro.

Origem

22. Por que os programadores JavaScript estão tendo problemas para usar a palavra-chave this?


Dificuldade: *****


A coisa mais importante a entender thisé que as funções não têm um valor fixo this. Este valor depende de como a função é chamada. Se dissermos que uma função é chamada com algum valor específico this, isso significa que esse valor é determinado não durante a declaração da função, mas durante sua chamada. Aqui estão alguns recursos this:

  • Se a função for chamada na forma usual (ou seja, usando a construção view someFunc()), ela thisse referirá ao objeto global (no navegador, este window). Se o código for executado no modo estrito, um thisvalor será gravado em undefined.
  • Se a função for chamada como método de um objeto, a palavra-chave thisserá representada pelo objeto ao qual o método pertence.
  • call apply, this , call apply.
  • , this .
  • , new, this , prototype -.
  • Se a função foi criada usando o método bind , a palavra this- chave function será rigidamente vinculada ao valor passado bindcomo o primeiro argumento. Essa é a única exceção à regra de que funções não possuem um valor codificado this. As funções criadas usando bindsão imutáveis this.

Origem

23. Compare o uso da construção assíncrona / aguardada e geradores para implementar a mesma funcionalidade


Dificuldade: *****


  • Ao iterar um gerador usando um método, .next()cada chamada para esse método retorna um único valor usando a palavra-chave yield. Ao usar a construção assíncrona / espera, as expressões aguardam são executadas seqüencialmente.
  • O design assíncrono / espera simplifica a implementação de um caso de uso de gerador específico.
  • Os valores retornados pelo gerador sempre têm a forma {value: X, done: Boolean}, e as funções assíncronas retornam promessas resolvidas com o valor X, ou falham.
  • Uma função assíncrona pode ser convertida em um gerador usando promessas. A seguir, é apresentado um exemplo dessa conversão.

Aqui está a função assíncrona:

//  
async function init() {
    const res1 = await doTask1();
    console.log(res1);

    const res2 = await doTask2(res1);
    console.log(res2);

    const res3 = await doTask3(res2);
    console.log(res3);

    return res3;
}

init();

Aqui está um gerador semelhante.

//    
function runner(genFn) {
    const itr = genFn();

    function run(arg) {
        let result = itr.next(arg);

        if (result.done) {
            return result.value;
        } else {
            return Promise.resolve(result.value).then(run);
        }
    }

    return run;
}

//   runner    
runner(function* () {
    const res1 = await doTask1();
    console.log(res1);

    const res2 = await doTask2(res1);
    console.log(res2);

    const res3 = await doTask3(res2);
    console.log(res3);

    return res3;
});

Fonte

Caros leitores! Quais perguntas de JavaScript você formulou durante suas entrevistas?


All Articles