JavaScript

Explique a delegação de eventos

A delegação de eventos é uma técnica que envolve adicionar ouvintes de eventos a um elemento pai em vez de adicioná-los aos elementos descendentes. O ouvinte será acionado sempre que o evento for disparado nos elementos descendentes devido ao borbulhamento de eventos pelo DOM. Os benefícios dessa técnica são:

  • O consumo de memória diminui porque apenas um único manipulador é necessário no elemento pai, em vez de ter que anexar manipuladores de eventos em cada descendente.
  • Não há necessidade de desvincular o manipulador de elementos que são removidos e de vincular o evento para novos elementos.

Explique como o `this` funciona em JavaScript

Não há uma explicação simples para this; é um dos conceitos mais confusos em JavaScript. Uma explicação geral é que o valor de this depende de como a função é chamada. Eu li muitas explicações sobre this online e achei a explicação de [Arnav Aggrawal] a mais clara. As seguintes regras são aplicadas:

  1. Se a palavra-chave new for usada ao chamar a função, this dentro da função é um objeto totalmente novo.
  2. Se apply, call ou bind forem usados para chamar/criar uma função, this dentro da função é o objeto que é passado como argumento.
  3. Se uma função é chamada como um método, como obj.method()this é o objeto do qual a função é uma propriedade.
  4. Se uma função é invocada como uma invocação de função livre, o que significa que foi invocada sem nenhuma das condições presentes acima, this é o objeto global. Em um navegador, é o objeto window. Se em modo estrito ('use strict'), this será undefined em vez do objeto global.
  5. Se várias das regras acima se aplicam, a regra que está mais acima vence e definirá o valor de this.
  6. Se a função é uma função de seta ES2015, ela ignora todas as regras acima e recebe o valor this de seu escopo circundante no momento em que é criada.

Para uma explicação aprofundada, confira o [artigo dele no Medium].

Você pode dar um exemplo de uma das maneiras pelas quais o trabalho com this mudou no ES6?

O ES6 permite que você use [funções de seta] que usam o [escopo léxico de fechamento]. Isso geralmente é conveniente, mas impede que o chamador controle o contexto via .call ou .apply — as consequências sendo que uma biblioteca como jQuery não vinculará corretamente this em suas funções de manipulador de eventos. Assim, é importante ter isso em mente ao refatorar grandes aplicações legadas.

Explique como funciona a herança prototípica

Todos os objetos JavaScript têm uma propriedade __proto__ (com exceção de objetos criados com Object.create(null)) que é uma referência a outro objeto, chamado de "protótipo" do objeto. Quando uma propriedade é acessada em um objeto e não é encontrada nesse objeto, o motor JavaScript procura no __proto__ do objeto, depois no __proto__ do __proto__ e assim por diante, até encontrar a propriedade definida em um dos __proto__s ou até atingir o final da cadeia de protótipos. Esse comportamento simula a herança clássica, mas é na verdade mais [delegação do que herança].

Exemplo de Herança Prototípica

// Construtor do objeto pai. function Animal(name) { this.name = name; } // Adiciona um método ao protótipo do objeto pai. Animal.prototype.makeSound = function () { console.log('The ' + this.constructor.name + ' makes a sound.'); }; // Construtor do objeto filho. function Dog(name) { Animal.call(this, name); // Chama o construtor pai. } // Define o protótipo do objeto filho como o protótipo do pai. Object.setPrototypeOf(Dog.prototype, Animal.prototype); // Adiciona um método ao protótipo do objeto filho. Dog.prototype.bark = function () { console.log('Woof!'); }; // Cria uma nova instância de Dog. const bolt = new Dog('Bolt'); // Chama métodos no objeto filho. console.log(bolt.name); // "Bolt" bolt.makeSound(); // "The Dog makes a sound." bolt.bark(); // "Woof!"

Coisas a serem observadas são:

  • .makeSound não está definido em Dog, então o motor sobe na cadeia de protótipos e encontra .makeSound a partir do Animal herdado.
  • O uso de Object.create para construir a cadeia de herança não é mais recomendado. Use Object.setPrototypeOf em vez disso.

O que você pensa sobre AMD vs CommonJS?

Ambos são formas de implementar um sistema de módulos, algo que não estava presente nativamente no JavaScript até o ES2015. O CommonJS é síncrono, enquanto o AMD (Asynchronous Module Definition) é assíncrono. O CommonJS é projetado com o desenvolvimento do lado do servidor em mente, enquanto o AMD, com seu suporte para carregamento assíncrono de módulos, é mais voltado para navegadores.

Acho a sintaxe AMD bastante verbosa, e o CommonJS é mais próximo do estilo de instruções de importação que você escreveria em outras linguagens. Na maioria das vezes, acho o AMD desnecessário, porque se você servisse todo o seu JavaScript em um único arquivo de pacote concatenado, não se beneficiaria das propriedades de carregamento assíncrono. Além disso, a sintaxe CommonJS é mais próxima do estilo Node de escrita de módulos, e há menos sobrecarga de mudança de contexto ao alternar entre o desenvolvimento JavaScript do lado do cliente e do lado do servidor.

Fico feliz que, com os módulos ES2015, que têm suporte para carregamento síncrono e assíncrono, finalmente podemos nos ater a uma única abordagem. Embora ainda não tenha sido totalmente implementado em navegadores e no Node, sempre podemos usar transpiladores para converter nosso código.

Explique por que o seguinte não funciona como um IIFE: `function foo(){ }();`. O que precisa ser alterado para torná-lo um IIFE corretamente?

IIFE significa Immediately Invoked Function Expression (Expressão de Função Invocada Imediatamente). O analisador JavaScript lê function foo(){ }(); como function foo(){ } e ();, onde o primeiro é uma declaração de função e o segundo (um par de parênteses) é uma tentativa de chamar uma função, mas não há nome especificado, o que resulta em Uncaught SyntaxError: Unexpected token ).

Existem duas maneiras de corrigir isso, que envolvem adicionar mais parênteses: (function foo(){ })() e (function foo(){ }()). Instruções que começam com function são consideradas declarações de função; ao envolver essa função em (), ela se torna uma expressão de função, que pode então ser executada com o subsequente (). Essas funções não são expostas no escopo global e você pode até omitir o nome se não precisar referenciá-la dentro do corpo.

Você também pode usar o operador void: void function foo(){ }();. Infelizmente, há um problema com essa abordagem. A avaliação da expressão dada é sempre undefined, então se sua função IIFE retornar algo, você não poderá usá-lo. Um exemplo:

const foo = void (function bar() { return 'foo'; })(); console.log(foo); // undefined

Qual é a diferença entre uma variável que é: `null`, `undefined` ou não declarada? Como você faria para verificar qualquer um desses estados?

Variáveis não declaradas são criadas quando você atribui um valor a um identificador que não foi previamente criado usando var, let ou const. Variáveis não declaradas serão definidas globalmente, fora do escopo atual. Em modo estrito, um ReferenceError será lançado quando você tentar atribuir a uma variável não declarada. Variáveis não declaradas são ruins, assim como variáveis globais são ruins. Evite-as a todo custo! Para verificá-las, envolva seu uso em um bloco try/catch.

function foo() { x = 1; // Lança um ReferenceError em modo estrito } foo(); console.log(x); // 1

Uma variável que é undefined é uma variável que foi declarada, mas não teve um valor atribuído. É do tipo undefined. Se uma função não retorna nenhum valor como resultado de sua execução e é atribuída a uma variável, a variável também tem o valor undefined. Para verificá-la, compare usando o operador de igualdade estrita (===) ou typeof, que retornará a string 'undefined'. Observe que você não deve usar o operador de igualdade abstrata para verificar, pois ele também retornará true se o valor for null.

var foo; console.log(foo); // undefined console.log(foo === undefined); // true console.log(typeof foo === 'undefined'); // true console.log(foo == null); // true. Errado, não use isso para verificar! function bar() {} var baz = bar(); console.log(baz); // undefined

Uma variável que é null terá sido explicitamente atribuída ao valor null. Ela representa nenhum valor e é diferente de undefined no sentido de que foi explicitamente atribuída. Para verificar null, basta comparar usando o operador de igualdade estrita. Observe que, como acima, você não deve usar o operador de igualdade abstrata (==) para verificar, pois ele também retornará true se o valor for undefined.

var foo = null; console.log(foo === null); // true console.log(typeof foo === 'object'); // true console.log(foo == undefined); // true. Errado, não use isso para verificar!

Como um hábito pessoal, eu nunca deixo minhas variáveis não declaradas ou não atribuídas. Atribuirei explicitamente null a elas após declarar se não pretendo usá-las ainda. Se você usa um linter em seu fluxo de trabalho, ele geralmente também será capaz de verificar se você não está referenciando variáveis não declaradas.

O que é um closure e como/por que você usaria um?

Um closure é a combinação de uma função e do ambiente léxico dentro do qual essa função foi declarada. A palavra "léxico" refere-se ao fato de que o escopo léxico usa a localização onde uma variável é declarada no código-fonte para determinar onde essa variável está disponível. Closures são funções que têm acesso às variáveis da função externa (envolvente) — cadeia de escopo — mesmo depois que a função externa retornou.

Por que você usaria um?

  • Privacidade de dados / emulação de métodos privados com closures. Comumente usado no [padrão de módulo].
  • [Aplicações parciais ou currying].

Você pode descrever a principal diferença entre um loop `.forEach` e um loop `.map()` e por que você escolheria um em vez do outro?

Para entender as diferenças entre os dois, vamos ver o que cada função faz.

forEach

  • Itera pelos elementos de um array.
  • Executa um callback para cada elemento.
  • Não retorna um valor.
const a = [1, 2, 3]; const doubled = a.forEach((num, index) => { // Faz algo com num e/ou index. }); // doubled = undefined

map

  • Itera pelos elementos de um array.
  • "Mapeia" cada elemento para um novo elemento chamando a função em cada elemento, criando um novo array como resultado.
const a = [1, 2, 3]; const doubled = a.map((num) => { return num * 2; }); // doubled = [2, 4, 6]

A principal diferença entre .forEach e .map() é que o .map() retorna um novo array. Se você precisa do resultado, mas não deseja mutar o array original, .map() é a escolha óbvia. Se você simplesmente precisa iterar sobre um array, forEach é uma boa escolha.

Qual é um caso de uso típico para funções anônimas?

Elas podem ser usadas em IIFEs para encapsular código dentro de um escopo local, evitando que variáveis vazem para o escopo global.

(function () { // Algum código aqui. })();

Como um callback que é usado uma vez e não precisa ser reutilizado em outro lugar. O código parecerá mais autocontido e legível quando os manipuladores são definidos diretamente onde são chamados, em vez de ter que procurar em outro lugar pelo corpo da função.

setTimeout(function () { console.log('Olá mundo!'); }, 1000);

Argumentos para construções de programação funcional ou Lodash (semelhante a callbacks).

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

Como você organiza seu código? (padrão de módulo, herança clássica?)

No passado, eu usei o Backbone para meus modelos, o que incentiva uma abordagem mais OOP, criando modelos Backbone e anexando métodos a eles.

O padrão de módulo ainda é ótimo, mas hoje em dia, eu uso React/Redux, que utilizam um fluxo de dados unidirecional baseado na arquitetura Flux. Eu representaria os modelos do meu aplicativo usando objetos simples e escreveria funções puras utilitárias para manipular esses objetos. O estado é manipulado usando ações e redutores como em qualquer outra aplicação Redux.

Evito usar herança clássica sempre que possível. Quando e se o faço, sigo [essas regras].

Qual a diferença entre objetos de host e objetos nativos?

Objetos nativos são objetos que fazem parte da linguagem JavaScript, definidos pela especificação ECMAScript, como String, Math, RegExp, Object, Function, etc.

Objetos de host são fornecidos pelo ambiente de tempo de execução (navegador ou Node), como window, XMLHTTPRequest, etc.

Diferença entre: `function Person(){}`, `var person = Person()`, e `var person = new Person()`?

Esta pergunta é bastante vaga. Minha melhor suposição é que ela está perguntando sobre construtores em JavaScript. Tecnicamente falando, function Person(){} é apenas uma declaração de função normal. A convenção é usar PascalCase para funções que se destinam a serem usadas como construtores.

var person = Person() invoca Person como uma função regular, e não como um construtor. Invocar dessa forma é um erro comum se a função se destina a ser usada como um construtor. Tipicamente, o construtor não retorna nada, portanto, invocar o construtor como uma função normal retornará undefined, e esse valor será atribuído à variável destinada como a instância.

var person = new Person() cria uma instância do objeto Person usando o operador new, que herda de Person.prototype. Uma alternativa seria usar Object.create, como: Object.create(Person.prototype).

function Person(name) { this.name = name; } var person = Person('John'); console.log(person); // undefined console.log(person.name); // Uncaught TypeError: Cannot read property 'name' of undefined var person = new Person('John'); console.log(person); // Person { name: "John" } console.log(person.name); // "john"

Qual a diferença entre `.call` e `.apply`?

Ambos .call e .apply são usados para invocar funções e o primeiro parâmetro será usado como o valor de this dentro da função. No entanto, .call aceita argumentos separados por vírgulas como os próximos argumentos, enquanto .apply aceita um array de argumentos como o próximo argumento. Uma maneira fácil de lembrar isso é C para call e argumentos cseparados por vírgula e A para apply e um array de argumentos.

function add(a, b) { return a + b; } console.log(add.call(null, 1, 2)); // 3 console.log(add.apply(null, [1, 2])); // 3

Explique `Function.prototype.bind`.

Retirado palavra por palavra do [MDN]:

O método bind() cria uma nova função que, quando chamada, tem sua palavra-chave this definida para o valor fornecido, com uma sequência de argumentos fornecida antes de quaisquer outros fornecidos quando a nova função é chamada.

Na minha experiência, é mais útil para vincular o valor de this em métodos de classes que você deseja passar para outras funções. Isso é feito frequentemente em componentes React.

Quando você usaria `document.write()`?

document.write() escreve uma string de texto para um fluxo de documento aberto por document.open(). Quando document.write() é executado após o carregamento da página, ele chamará document.open, que limpa todo o documento (<head> e <body> removidos!) e substitui o conteúdo pelo valor do parâmetro fornecido. Por isso, geralmente é considerado perigoso e propenso a uso indevido.

Existem algumas respostas online que explicam que document.write() é usado em código de análise ou [quando você deseja incluir estilos que só devem funcionar se o JavaScript estiver habilitado]. Ele é até mesmo usado no HTML5 boilerplate para [carregar scripts em paralelo e preservar a ordem de execução]! No entanto, suspeito que essas razões podem estar desatualizadas e, nos dias modernos, podem ser alcançadas sem usar document.write(). Por favor, me corrija se eu estiver errado sobre isso.

Qual a diferença entre detecção de recursos, inferência de recursos e uso da string UA?

Detecção de Recursos

A detecção de recursos envolve descobrir se um navegador suporta um determinado bloco de código e executar um código diferente dependendo se ele suporta (ou não), para que o navegador possa sempre fornecer uma experiência funcional em vez de travar/erro em alguns navegadores. Por exemplo:

if ('geolocation' in navigator) { // Pode usar navigator.geolocation } else { // Lidar com a falta de recurso }

[Modernizr] é uma ótima biblioteca para lidar com a detecção de recursos.

Inferência de Recursos

A inferência de recursos verifica um recurso assim como a detecção de recursos, mas usa outra função porque assume que ela também existirá, por exemplo:

if (document.getElementsByTagName) { element = document.getElementById(id); }

Isso não é realmente recomendado. A detecção de recursos é mais infalível.

String UA

Esta é uma string relatada pelo navegador que permite que os pares de protocolo de rede identifiquem o tipo de aplicativo, sistema operacional, fornecedor de software ou versão de software do agente de usuário do software solicitante. Ela pode ser acessada via navigator.userAgent. No entanto, a string é difícil de analisar e pode ser falsificada. Por exemplo, o Chrome relata-se como Chrome e Safari. Portanto, para detectar o Safari, você precisa verificar a string do Safari e a ausência da string do Chrome. Evite este método.

Explique o Ajax com o máximo de detalhes possível.

Ajax (JavaScript assíncrono e XML) é um conjunto de técnicas de desenvolvimento web que utilizam várias tecnologias web no lado do cliente para criar aplicações web assíncronas. Com o Ajax, as aplicações web podem enviar e recuperar dados de um servidor de forma assíncrona (em segundo plano) sem interferir na exibição e comportamento da página existente. Ao desacoplar a camada de intercâmbio de dados da camada de apresentação, o Ajax permite que as páginas web, e por extensão as aplicações web, alterem o conteúdo dinamicamente sem a necessidade de recarregar a página inteira. Na prática, implementações modernas comumente usam JSON em vez de XML, devido às vantagens do JSON ser nativo do JavaScript.

A API XMLHttpRequest é frequentemente usada para a comunicação assíncrona ou, atualmente, a API fetch().

Quais são as vantagens e desvantagens de usar Ajax?

Vantagens

  • Melhor interatividade. Novo conteúdo do servidor pode ser alterado dinamicamente sem a necessidade de recarregar a página inteira.
  • Reduz conexões ao servidor já que scripts e folhas de estilo só precisam ser solicitados uma vez.
  • O estado pode ser mantido em uma página. Variáveis JavaScript e estado do DOM persistirão porque a página do contêiner principal não foi recarregada.
  • Basicamente, a maioria das vantagens de um SPA.

Desvantagens

  • Páginas web dinâmicas são mais difíceis de marcar como favoritas.
  • Não funciona se o JavaScript estiver desabilitado no navegador.
  • Alguns rastreadores da web não executam JavaScript e não veriam o conteúdo que foi carregado por JavaScript.
  • Páginas web que usam Ajax para buscar dados provavelmente terão que combinar os dados remotos buscados com modelos do lado do cliente para atualizar o DOM. Para que isso aconteça, o JavaScript terá que ser analisado e executado no navegador, e dispositivos móveis de baixo custo podem ter dificuldades com isso.
  • Basicamente, a maioria das desvantagens de um SPA.

Explique como o JSONP funciona (e como ele não é realmente Ajax).

JSONP (JSON com Padding) é um método comumente usado para contornar as políticas de cross-domain em navegadores web, pois as requisições Ajax da página atual para um domínio de origem cruzada não são permitidas.

O JSONP funciona fazendo uma requisição para um domínio de origem cruzada via uma tag <script> e geralmente com um parâmetro de consulta callback, por exemplo: https://example.com?callback=printData. O servidor então envolverá os dados dentro de uma função chamada printData e os retornará ao cliente.

<script> function printData(data) { console.log(`Meu nome é ${data.name}!`); } </script> <script src="[https://example.com?callback=printData](https://example.com?callback=printData)"></script>
printData({ name: 'Yang Shun' });

O cliente precisa ter a função printData em seu escopo global e a função será executada pelo cliente quando a resposta do domínio de origem cruzada for recebida.

JSONP pode ser inseguro e tem algumas implicações de segurança. Como JSONP é realmente JavaScript, ele pode fazer tudo o que o JavaScript pode fazer, então você precisa confiar no provedor dos dados JSONP.

Atualmente, [CORS] é a abordagem recomendada e JSONP é visto como um hack.

Você já usou templating JavaScript? Se sim, quais bibliotecas você usou?

Sim. Handlebars, Underscore, Lodash, AngularJS e JSX. Eu não gostava de templating no AngularJS porque fazia uso pesado de strings nas diretivas e erros de digitação passavam despercebidos. JSX é meu novo favorito, pois é mais próximo do JavaScript e quase não há sintaxe para aprender. Hoje em dia, você pode até usar literais de string de template ES2015 como uma maneira rápida de criar templates sem depender de código de terceiros.

const template = `<div>Meu nome é: ${name}</div>`;

No entanto, esteja ciente de um potencial XSS na abordagem acima, pois o conteúdo não é escapado para você, ao contrário das bibliotecas de templating.

Explique "hoisting".

Hoisting é um termo usado para explicar o comportamento das declarações de variáveis em seu código. Variáveis declaradas ou inicializadas com a palavra-chave var terão sua declaração "movida" para o topo de seu escopo de módulo/função, que chamamos de hoisting. No entanto, apenas a declaração é hoisted, a atribuição (se houver) permanecerá onde está.

Observe que a declaração não é realmente movida - o motor JavaScript analisa as declarações durante a compilação e se torna ciente das declarações e seus escopos. É apenas mais fácil entender esse comportamento visualizando as declarações como sendo elevadas para o topo de seu escopo. Vamos explicar com alguns exemplos.

console.log(foo); // undefined var foo = 1; console.log(foo); // 1

Declarações de função têm o corpo hoisted, enquanto as expressões de função (escritas na forma de declarações de variável) têm apenas a declaração da variável hoisted.

// Declaração de Função console.log(foo); // [Function: foo] foo(); // 'FOOOOO' function foo() { console.log('FOOOOO'); } console.log(foo); // [Function: foo] // Expressão de Função console.log(bar); // undefined bar(); // Uncaught TypeError: bar is not a function var bar = function () { console.log('BARRRR'); }; console.log(bar); // [Function: bar]

Variáveis declaradas via let e const também são hoisted. No entanto, ao contrário de var e function, elas não são inicializadas e o acesso a elas antes da declaração resultará em uma exceção ReferenceError. A variável está em uma "zona morta temporal" desde o início do bloco até que a declaração seja processada.

x; // undefined y; // Reference error: y is not defined var x = 'local'; let y = 'local';

Descreva o borbulhamento de eventos.

Quando um evento é disparado em um elemento DOM, ele tentará manipular o evento se houver um ouvinte anexado. Em seguida, o evento é borbulhado para seu elemento pai e a mesma coisa acontece. Esse borbulhamento ocorre subindo os ancestrais do elemento até o document. O borbulhamento de eventos é o mecanismo por trás da delegação de eventos.

Qual a diferença entre um "atributo" e uma "propriedade"?

Atributos são definidos na marcação HTML, mas propriedades são definidas no DOM. Para ilustrar a diferença, imagine que temos este campo de texto em nosso HTML: <input type="text" value="Hello">.

const input = document.querySelector('input'); console.log(input.getAttribute('value')); // Hello console.log(input.value); // Hello

Mas depois de você mudar o valor do campo de texto adicionando "World!" a ele, isso se torna:

console.log(input.getAttribute('value')); // Hello console.log(input.value); // Hello World!

Por que estender objetos JavaScript embutidos não é uma boa ideia?

Estender um objeto JavaScript embutido/nativo significa adicionar propriedades/funções ao seu prototype. Embora isso possa parecer uma boa ideia a princípio, é perigoso na prática. Imagine que seu código usa algumas bibliotecas que ambas estendem o Array.prototype adicionando o mesmo método contains; as implementações sobrescreverão umas às outras e seu código falhará se o comportamento desses dois métodos não for o mesmo.

A única vez em que você pode querer estender um objeto nativo é quando você deseja criar um polyfill, essencialmente fornecendo sua própria implementação para um método que faz parte da especificação JavaScript, mas pode não existir no navegador do usuário devido a ser um navegador mais antigo.

Diferença entre o evento `load` do documento e o evento `DOMContentLoaded` do documento?

O evento DOMContentLoaded é disparado quando o documento HTML inicial foi completamente carregado e analisado, sem esperar por stylesheets, imagens e subframes.

O evento load da window só é disparado depois que o DOM e todos os recursos e ativos dependentes foram carregados.

Qual a diferença entre `==` e `===`?

== é o operador de igualdade abstrata, enquanto === é o operador de igualdade estrita. O operador == comparará a igualdade após fazer as conversões de tipo necessárias. O operador === não fará conversão de tipo, então se dois valores não forem do mesmo tipo, === simplesmente retornará false. Ao usar ==, coisas estranhas podem acontecer, como:

1 == '1'; // true 1 == [1]; // true 1 == true; // true 0 == ''; // true 0 == '0'; // true 0 == false; // true

Meu conselho é nunca usar o operador ==, exceto por conveniência ao comparar com null ou undefined, onde a == null retornará true se a for null ou undefined.

var a = null; console.log(a == null); // true console.log(a == undefined); // true

Explique a política de mesma origem em relação ao JavaScript.

A política de mesma origem impede que o JavaScript faça requisições entre domínios diferentes. Uma origem é definida como uma combinação de esquema URI, nome do host e número da porta. Essa política impede que um script malicioso em uma página obtenha acesso a dados confidenciais em outra página da web por meio do Document Object Model dessa página.

Faça isso funcionar:

duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]
function duplicate(arr) { return arr.concat(arr); } duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

Ou com ES6:

const duplicate = (arr) => [...arr, ...arr]; duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

Por que é chamado de expressão ternária, o que a palavra "Ternário" indica?

"Ternário" indica três, e uma expressão ternária aceita três operandos: a condição de teste, a expressão "então" e a expressão "senão". Expressões ternárias não são específicas do JavaScript e não tenho certeza por que está nesta lista.

O que é `"use strict";`? Quais são as vantagens e desvantagens de usá-lo?

'use strict' é uma declaração usada para habilitar o modo estrito para scripts inteiros ou funções individuais. O modo estrito é uma forma de optar por uma variante restrita do JavaScript.

Vantagens:

  • Torna impossível criar acidentalmente variáveis globais.
  • Faz com que atribuições que, de outra forma, falhariam silenciosamente, lancem uma exceção.
  • Faz com que tentativas de excluir propriedades não deletáveis lancem uma exceção (onde antes a tentativa simplesmente não teria efeito).
  • Requer que os nomes dos parâmetros da função sejam únicos.
  • this é indefinido no contexto global.
  • Captura alguns erros de codificação comuns, lançando exceções.
  • Desabilita recursos que são confusos ou mal pensados.

Desvantagens:

  • Muitos recursos ausentes que alguns desenvolvedores podem estar acostumados.
  • Não há mais acesso a function.caller e function.arguments.
  • A concatenação de scripts escritos em diferentes modos estritos pode causar problemas.

No geral, acho que os benefícios superam as desvantagens, e nunca precisei depender dos recursos que o modo estrito bloqueia. Eu recomendaria usar o modo estrito.

Crie um loop `for` que itere até `100`, enquanto imprime "fizz" para múltiplos de `3`, "buzz" para múltiplos de `5` e "fizzbuzz" para múltiplos de `3` e `5`.

Confira esta versão do FizzBuzz de [Paul Irish].

for (let i = 1; i <= 100; i++) { let f = i % 3 == 0, b = i % 5 == 0; console.log(f ? (b ? 'FizzBuzz' : 'Fizz') : b ? 'Buzz' : i); }

Eu não aconselharia você a escrever o acima durante as entrevistas, porém. Apenas siga a abordagem longa, mas clara. Para versões mais malucas do FizzBuzz, confira o link de referência abaixo.

Por que é, em geral, uma boa ideia deixar o escopo global de um site como está e nunca tocá-lo?

Todo script tem acesso ao escopo global, e se todos usarem o namespace global para definir suas variáveis, colisões provavelmente ocorrerão. Use o padrão de módulo (IIFEs) para encapsular suas variáveis dentro de um namespace local.

Por que você usaria algo como o evento `load`? Este evento tem desvantagens? Você conhece alguma alternativa e por que as usaria?

O evento load é disparado no final do processo de carregamento do documento. Neste ponto, todos os objetos no documento estão no DOM, e todas as imagens, scripts, links e sub-frames terminaram de carregar.

O evento DOM DOMContentLoaded será disparado após a construção do DOM para a página, mas não espera que outros recursos terminem de carregar. Isso é preferido em certos casos quando você não precisa que a página inteira seja carregada antes da inicialização.

Explique o que é um SPA (Single Page Application) e como torná-lo amigável ao SEO.

O texto abaixo foi retirado do incrível [Grab Front End Guide], que, coincidentemente, foi escrito por mim!

Desenvolvedores web hoje em dia se referem aos produtos que constroem como web apps, em vez de websites. Embora não haja uma diferença estrita entre os dois termos, web apps tendem a ser altamente interativos e dinâmicos, permitindo que o usuário realize ações e receba uma resposta à sua ação. Tradicionalmente, o navegador recebe HTML do servidor e o renderiza. Quando o usuário navega para outra URL, uma atualização de página completa é necessária e o servidor envia um novo HTML para a nova página. Isso é chamado de renderização no lado do servidor.

No entanto, em SPAs modernas, a renderização no lado do cliente é usada. O navegador carrega a página inicial do servidor, juntamente com os scripts (frameworks, bibliotecas, código do aplicativo) e folhas de estilo necessários para todo o aplicativo. Quando o usuário navega para outras páginas, uma atualização de página não é acionada. A URL da página é atualizada através da [API de Histórico HTML5]. Novos dados necessários para a nova página, geralmente em formato JSON, são recuperados pelo navegador via requisições [AJAX] para o servidor. O SPA então atualiza dinamicamente a página com os dados via JavaScript, que já foi baixado no carregamento inicial da página. Este modelo é semelhante a como os aplicativos móveis nativos funcionam.

Os benefícios:

  • O aplicativo parece mais responsivo e os usuários não veem o 'flash' entre as navegações de página devido a atualizações completas de página.
  • Menos requisições HTTP são feitas ao servidor, já que os mesmos ativos não precisam ser baixados novamente para cada carregamento de página.
  • Clara separação das preocupações entre o cliente e o servidor; você pode facilmente construir novos clientes para diferentes plataformas (por exemplo, mobile, chatbots, smart watches) sem ter que modificar o código do servidor. Você também pode modificar a pilha de tecnologia no cliente e no servidor independentemente, desde que o contrato da API não seja quebrado.

As desvantagens:

  • Carregamento inicial de página mais pesado devido ao carregamento do framework, código do aplicativo e ativos necessários para várias páginas.
  • Há uma etapa adicional a ser feita no seu servidor, que é configurá-lo para rotear todas as requisições para um único ponto de entrada e permitir que o roteamento do lado do cliente assuma a partir daí.
  • SPAs dependem do JavaScript para renderizar conteúdo, mas nem todos os mecanismos de busca executam JavaScript durante o rastreamento, e eles podem ver conteúdo vazio em sua página. Isso prejudica inadvertidamente a Otimização para Mecanismos de Busca (SEO) do seu aplicativo. No entanto, na maioria das vezes, ao construir aplicativos, o SEO não é o fator mais importante, pois nem todo o conteúdo precisa ser indexável por mecanismos de busca. Para superar isso, você pode renderizar seu aplicativo no lado do servidor ou usar serviços como [Prerender] para "renderizar seu javascript em um navegador, salvar o HTML estático e retorná-lo aos rastreadores".

Qual a extensão da sua experiência com Promises e/ou seus polyfills?

Possuo conhecimento prático. Uma Promise é um objeto que pode produzir um único valor em algum momento no futuro: ou um valor resolvido ou uma razão pela qual não foi resolvido (por exemplo, ocorreu um erro de rede). Uma Promise pode estar em um dos 3 estados possíveis: cumprida, rejeitada ou pendente. Os usuários de Promises podem anexar callbacks para lidar com o valor cumprido ou o motivo da rejeição.

Alguns polyfills comuns são $.deferred, Q e Bluebird, mas nem todos eles estão em conformidade com a especificação. O ES2015 suporta Promises nativamente, e polyfills geralmente não são necessários hoje em dia.

Quais são os prós e os contras de usar Promises em vez de callbacks?

Prós

  • Evita o inferno de callbacks, que pode ser ilegível.
  • Facilita a escrita de código assíncrono sequencial legível com .then().
  • Facilita a escrita de código assíncrono paralelo com Promise.all().
  • Com Promises, esses cenários, que estão presentes na codificação apenas com callbacks, não acontecerão:
    • Chamar o callback muito cedo
    • Chamar o callback muito tarde (ou nunca)
    • Chamar o callback poucas ou muitas vezes
    • Não passar nenhum ambiente/parâmetros necessários
    • Engolir quaisquer erros/exceções que possam ocorrer

Contras

  • Código ligeiramente mais complexo (discutível).
  • Em navegadores mais antigos onde o ES2015 não é suportado, você precisa carregar um polyfill para usá-lo.

Quais são algumas das vantagens/desvantagens de escrever código JavaScript em uma linguagem que compila para JavaScript?

Alguns exemplos de linguagens que compilam para JavaScript incluem CoffeeScript, Elm, ClojureScript, PureScript e TypeScript.

Vantagens:

  • Corrige alguns dos problemas de longa data no JavaScript e desencoraja os antipadrões do JavaScript.
  • Permite escrever um código mais curto, fornecendo um açúcar sintático além do JavaScript, o que eu acho que falta no ES5, mas o ES2015 é incrível.
  • Tipos estáticos são incríveis (no caso do TypeScript) para grandes projetos que precisam ser mantidos ao longo do tempo.

Desvantagens:

  • Requer um processo de construção/compilação, pois os navegadores só executam JavaScript e seu código precisará ser compilado para JavaScript antes de ser servido aos navegadores.
  • A depuração pode ser um problema se seus mapas de origem não se mapearem bem para sua origem pré-compilada.
  • A maioria dos desenvolvedores não está familiarizada com essas linguagens e precisará aprendê-las. Há um custo de adaptação envolvido para sua equipe se você as usar em seus projetos.
  • Comunidade menor (depende da linguagem), o que significa que recursos, tutoriais, bibliotecas e ferramentas seriam mais difíceis de encontrar.
  • O suporte a IDE/editor pode ser deficiente.
  • Essas linguagens estarão sempre atrasadas em relação ao padrão JavaScript mais recente.
  • Os desenvolvedores devem estar cientes do que seu código está sendo compilado – porque é isso que realmente estará em execução, e isso é o que importa no final.

Praticamente, o ES2015 melhorou muito o JavaScript e o tornou muito mais agradável de escrever. Eu realmente não vejo a necessidade do CoffeeScript hoje em dia.

Quais ferramentas e técnicas você usa para depurar código JavaScript?

  • React e Redux
    • [React Devtools]
    • [Redux Devtools]
  • Vue
    • [Vue Devtools]
  • JavaScript
    • [Chrome Devtools]
    • debugger statement
    • O bom e velho console.log para depuração

Quais construções de linguagem você usa para iterar sobre propriedades de objetos e itens de array?

Para objetos:

  • Loops for-in - for (var property in obj) { console.log(property); }. No entanto, isso também irá iterar por suas propriedades herdadas, e você adicionará uma verificação obj.hasOwnProperty(property) antes de usá-lo.
  • Object.keys() - Object.keys(obj).forEach(function (property) { ... }). Object.keys() é um método estático que listará todas as propriedades enumeráveis do objeto que você passar a ele.
  • Object.getOwnPropertyNames() - Object.getOwnPropertyNames(obj).forEach(function (property) { ... }). Object.getOwnPropertyNames() é um método estático que listará todas as propriedades enumeráveis e não enumeráveis do objeto que você passar a ele.

Para arrays:

  • Loops for - for (var i = 0; i < arr.length; i++). A armadilha comum aqui é que var está no escopo da função e não no escopo do bloco, e na maioria das vezes você gostaria de uma variável de iterador com escopo de bloco. O ES2015 introduz let, que tem escopo de bloco, e é recomendado usar isso em vez disso. Então isso se torna: for (let i = 0; i < arr.length; i++).
  • forEach - arr.forEach(function (el, index) { ... }). Essa construção pode ser mais conveniente às vezes porque você não precisa usar o index se tudo o que precisa são os elementos do array. Existem também os métodos every e some que permitirão que você termine a iteração mais cedo.
  • Loops for-of - for (let elem of arr) { ... }. O ES6 introduz um novo loop, o loop for-of, que permite iterar sobre objetos que estão em conformidade com o protocolo iterável, como String, Array, Map, Set, etc. Ele combina as vantagens do loop for e do método forEach(). A vantagem do loop for é que você pode interrompê-lo, e a vantagem de forEach() é que ele é mais conciso do que o loop for porque você não precisa de uma variável de contador. Com o loop for-of, você obtém a capacidade de interromper um loop e uma sintaxe mais concisa.

Na maioria das vezes, eu preferiria o método .forEach, mas isso realmente depende do que você está tentando fazer. Antes do ES6, usávamos loops for quando precisávamos terminar prematuramente o loop usando break. Mas agora com o ES6, podemos fazer isso com loops for-of. Eu usaria loops for quando preciso de ainda mais flexibilidade, como incrementar o iterador mais de uma vez por loop.

Além disso, ao usar o loop for-of, se você precisar acessar o índice e o valor de cada elemento do array, você pode fazê-lo com o método entries() do Array do ES6 e desestruturação:

const arr = ['a', 'b', 'c']; for (let [index, elem] of arr.entries()) { console.log(index, ': ', elem); }

Explique a diferença entre objetos mutáveis e imutáveis.

A imutabilidade é um princípio central na programação funcional, e tem muito a oferecer também aos programas orientados a objetos. Um objeto mutável é um objeto cujo estado pode ser modificado após sua criação. Um objeto imutável é um objeto cujo estado não pode ser modificado após sua criação.

Qual é um exemplo de objeto imutável em JavaScript?

Em JavaScript, alguns tipos embutidos (números, strings) são imutáveis, mas objetos personalizados são geralmente mutáveis.

Alguns objetos JavaScript imutáveis embutidos são Math, Date.

Aqui estão algumas maneiras de adicionar/simular imutabilidade em objetos JavaScript simples.

Propriedades Constantes do Objeto

Combinando writable: false e configurable: false, você pode essencialmente criar uma constante (não pode ser alterada, redefinida ou excluída) como uma propriedade de objeto, como:

let myObject = {}; Object.defineProperty(myObject, 'number', { value: 42, writable: false, configurable: false, }); console.log(myObject.number); // 42 myObject.number = 43; console.log(myObject.number); // 42

Impedir Extensões

Se você deseja impedir que um objeto tenha novas propriedades adicionadas a ele, mas de outra forma deixar o resto das propriedades do objeto intactas, chame Object.preventExtensions(...):

var myObject = { a: 2, }; Object.preventExtensions(myObject); myObject.b = 3; myObject.b; // undefined

Em modo não estrito, a criação de b falha silenciosamente. Em modo estrito, ela lança um TypeError.

Selar

Object.seal() cria um objeto "selado", o que significa que ele pega um objeto existente e essencialmente chama Object.preventExtensions() nele, mas também marca todas as suas propriedades existentes como configurable: false.

Assim, você não pode adicionar mais propriedades, e também não pode reconfigurar ou excluir nenhuma propriedade existente (embora ainda possa modificar seus valores).

Congelar

Object.freeze() cria um objeto congelado, o que significa que ele pega um objeto existente e essencialmente chama Object.seal() nele, mas também marca todas as propriedades de "acesso a dados" como writable: false, para que seus valores não possam ser alterados.

Essa abordagem é o nível mais alto de imutabilidade que você pode obter para um objeto em si, pois impede quaisquer alterações no objeto ou em qualquer uma de suas propriedades diretas (embora, como mencionado acima, o conteúdo de quaisquer outros objetos referenciados não seja afetado).

var immutable = Object.freeze({});

Congelar um objeto não permite que novas propriedades sejam adicionadas a um objeto e impede a remoção ou alteração das propriedades existentes. Object.freeze() preserva a enumerabilidade, configurabilidade, gravabilidade e o protótipo do objeto. Ele retorna o objeto passado e não cria uma cópia congelada.

Quais são os prós e os contras da imutabilidade?

Prós

  • Detecção de mudanças mais fácil - A igualdade de objetos pode ser determinada de forma performática e fácil através da igualdade referencial. Isso é útil para comparar diferenças de objetos em React e Redux.
  • Programas com objetos imutáveis são menos complicados de pensar, pois você não precisa se preocupar com como um objeto pode evoluir ao longo do tempo.
  • Cópias defensivas não são mais necessárias quando objetos imutáveis são retornados ou passados para funções, pois não há possibilidade de um objeto imutável ser modificado por ela.
  • Compartilhamento fácil via referências - Uma cópia de um objeto é tão boa quanto outra, então você pode armazenar objetos em cache ou reutilizar o mesmo objeto várias vezes.
  • Thread-safe - Objetos imutáveis podem ser usados com segurança entre threads em um ambiente multi-threaded, pois não há risco de serem modificados em outras threads em execução simultânea.
  • Usando bibliotecas como ImmutableJS, objetos são modificados usando compartilhamento estrutural e menos memória é necessária para ter múltiplos objetos com estruturas semelhantes.

Contras

  • Implementações ingênuas de estruturas de dados imutáveis e suas operações podem resultar em desempenho extremamente ruim porque novos objetos são criados a cada vez. É recomendado usar bibliotecas para estruturas de dados imutáveis e operações eficientes que aproveitam o compartilhamento estrutural.
  • A alocação (e desalocação) de muitos objetos pequenos em vez de modificar os existentes pode causar um impacto no desempenho. A complexidade do alocador ou do coletor de lixo geralmente depende do número de objetos no heap.
  • Estruturas de dados cíclicas, como grafos, são difíceis de construir. Se você tem dois objetos que não podem ser modificados após a inicialização, como você pode fazê-los apontar um para o outro?

Como você pode obter imutabilidade em seu próprio código?

A alternativa é usar declarações const combinadas com as técnicas mencionadas acima para criação. Para "mutar" objetos, use o operador de espalhamento, Object.assign, Array.concat(), etc., para criar novos objetos em vez de mutar o objeto original.

Exemplos:

// Exemplo de Array const arr = [1, 2, 3]; const newArr = [...arr, 4]; // [1, 2, 3, 4] // Exemplo de Objeto const human = Object.freeze({ race: 'human' }); const john = { ...human, name: 'John' }; // {race: "human", name: "John"} const alienJohn = { ...john, race: 'alien' }; // {race: "alien", name: "John"}

Explique a diferença entre funções síncronas e assíncronas.

Funções síncronas são bloqueantes, enquanto funções assíncronas não são. Em funções síncronas, as instruções são concluídas antes que a próxima instrução seja executada. Nesse caso, o programa é avaliado exatamente na ordem das instruções e a execução do programa é pausada se uma das instruções demorar muito.

Funções assíncronas geralmente aceitam um callback como parâmetro e a execução continua na próxima linha imediatamente após a função assíncrona ser invocada. O callback só é invocado quando a operação assíncrona é concluída e a pilha de chamadas está vazia. Operações pesadas, como carregar dados de um servidor web ou consultar um banco de dados, devem ser feitas assincronamente para que o thread principal possa continuar executando outras operações em vez de bloquear até que essa operação demorada seja concluída (no caso de navegadores, a interface do usuário congelará).

O que é um loop de eventos? Qual a diferença entre a pilha de chamadas e a fila de tarefas?

O loop de eventos é um loop de thread único que monitora a pilha de chamadas e verifica se há algum trabalho a ser feito na fila de tarefas. Se a pilha de chamadas estiver vazia e houver funções de callback na fila de tarefas, uma função é removida da fila e empurrada para a pilha de chamadas para ser executada.

Se você ainda não assistiu à palestra de Philip Robert sobre o Loop de Eventos, deveria. É um dos vídeos mais vistos sobre JavaScript.

Explique as diferenças no uso de `foo` entre `function foo() {}` e `var foo = function() {}`

O primeiro é uma declaração de função, enquanto o segundo é uma expressão de função. A principal diferença é que as declarações de função têm seu corpo içado, mas os corpos das expressões de função não (elas têm o mesmo comportamento de içamento que as variáveis). Para mais explicações sobre içamento, consulte a pergunta acima sobre içamento. Se você tentar invocar uma expressão de função antes de ela ser definida, você receberá um erro Uncaught TypeError: XXX is not a function.

Declaração de Função

foo(); // 'FOOOOO' function foo() { console.log('FOOOOO'); }

Expressão de Função

foo(); // Uncaught TypeError: foo is not a function var foo = function () { console.log('FOOOOO'); };

Quais são as diferenças entre variáveis criadas usando `let`, `var` ou `const`?

Variáveis declaradas usando a palavra-chave var são escopadas para a função na qual são criadas, ou se criadas fora de qualquer função, para o objeto global. let e const são escopadas por bloco, o que significa que são acessíveis apenas dentro do conjunto mais próximo de chaves (função, bloco if-else ou loop for).

function foo() { // Todas as variáveis são acessíveis dentro das funções. var bar = 'bar'; let baz = 'baz'; const qux = 'qux'; console.log(bar); // bar console.log(baz); // baz console.log(qux); // qux } console.log(bar); // ReferenceError: bar is not defined console.log(baz); // ReferenceError: baz is not defined console.log(qux); // ReferenceError: qux is not defined
if (true) { var bar = 'bar'; let baz = 'baz'; const qux = 'qux'; } // Variáveis declaradas com var são acessíveis em qualquer lugar no escopo da função. console.log(bar); // bar // Variáveis definidas com let e const não são acessíveis fora do bloco em que foram definidas. console.log(baz); // ReferenceError: baz is not defined console.log(qux); // ReferenceError: qux is not defined

var permite que as variáveis sejam içadas, o que significa que elas podem ser referenciadas no código antes de serem declaradas. let e const não permitirão isso, lançando um erro em vez disso.

console.log(foo); // undefined var foo = 'foo'; console.log(baz); // ReferenceError: não pode acessar a declaração léxica 'baz' antes da inicialização let baz = 'baz'; console.log(bar); // ReferenceError: não pode acessar a declaração léxica 'bar' antes da inicialização const bar = 'bar';

Redeclarar uma variável com var não lançará um erro, mas let e const o farão.

var foo = 'foo'; var foo = 'bar'; console.log(foo); // "bar" let baz = 'baz'; let baz = 'qux'; // Uncaught SyntaxError: Identifier 'baz' has already been declared

let e const diferem no fato de que let permite reatribuir o valor da variável enquanto const não.

// Isso está ok. let foo = 'foo'; foo = 'bar'; // Isso causa uma exceção. const baz = 'baz'; baz = 'qux';

Quais são as diferenças entre a classe ES6 e os construtores de função ES5?

Vamos primeiro ver um exemplo de cada:

// Construtor de Função ES5 function Person(name) { this.name = name; } // Classe ES6 class Person { constructor(name) { this.name = name; } }

Para construtores simples, eles parecem bastante semelhantes.

A principal diferença no construtor surge ao usar herança. Se quisermos criar uma classe Student que subclasse Person e adicionar um campo studentId, é isso que temos que fazer além do acima.

// Construtor de Função ES5 function Student(name, studentId) { // Chame o construtor da superclasse para inicializar os membros derivados da superclasse. Person.call(this, name); // Inicialize os próprios membros da subclasse. 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; } }

É muito mais verboso usar herança no ES5 e a versão ES6 é mais fácil de entender e lembrar.

Você pode oferecer um caso de uso para a nova sintaxe de função arrow =>? Como essa nova sintaxe difere de outras funções?

Um benefício óbvio das funções de seta é simplificar a sintaxe necessária para criar funções, sem a necessidade da palavra-chave function. O this dentro das funções de seta também é vinculado ao escopo envolvente, o que é diferente em comparação com as funções regulares, onde o this é determinado pelo objeto que a chama. O this com escopo léxico é útil ao invocar callbacks, especialmente em componentes React.

Qual a vantagem de usar a sintaxe de seta para um método em um construtor?

A principal vantagem de usar uma função de seta como método dentro de um construtor é que o valor de this é definido no momento da criação da função e não pode mudar depois disso. Assim, quando o construtor é usado para criar um novo objeto, this sempre se referirá a esse objeto. Por exemplo, digamos que temos um construtor Person que recebe um primeiro nome como argumento e tem dois métodos para console.log esse nome, um como função regular e outro como função de seta:

const Person = function (firstName) { this.firstName = firstName; this.sayName1 = function () { console.log(this.firstName); }; this.sayName2 = () => { console.log(this.firstName); }; }; const john = new Person('John'); const dave = new Person('Dave'); john.sayName1(); // John john.sayName2(); // John // A função regular pode ter seu valor 'this' alterado, mas a função de seta não john.sayName1.call(dave); // Dave (porque "this" agora é o objeto dave) john.sayName2.call(dave); // John john.sayName1.apply(dave); // Dave (porque 'this' agora é o objeto dave) john.sayName2.apply(dave); // John john.sayName1.bind(dave)(); // Dave (porque 'this' agora é o objeto dave) john.sayName2.bind(dave)(); // John var sayNameFromWindow1 = john.sayName1; sayNameFromWindow1(); // undefined (porque 'this' agora é o objeto window) var sayNameFromWindow2 = john.sayName2; sayNameFromWindow2(); // John

A principal conclusão aqui é que this pode ser alterado para uma função normal, mas o contexto sempre permanece o mesmo para uma função de seta. Então, mesmo que você esteja passando sua função de seta para diferentes partes de seu aplicativo, você não precisaria se preocupar com a mudança de contexto.

Isso pode ser particularmente útil em componentes de classe React. Se você definir um método de classe para algo como um manipulador de clique usando uma função normal, e então passar esse manipulador de clique para um componente filho como uma prop, você precisará também vincular this no construtor do componente pai. Se você, em vez disso, usar uma função de seta, não há necessidade de vincular "this", pois o método obterá automaticamente seu valor "this" de seu contexto léxico envolvente.

Qual é a definição de uma função de ordem superior?

Uma função de ordem superior é qualquer função que recebe uma ou mais funções como argumentos, que ela usa para operar em alguns dados, e/ou retorna uma função como resultado. Funções de ordem superior são destinadas a abstrair alguma operação que é realizada repetidamente. O exemplo clássico disso é map, que recebe um array e uma função como argumentos. map então usa essa função para transformar cada item no array, retornando um novo array com os dados transformados. Outros exemplos populares em JavaScript são forEach, filter e reduce. Uma função de ordem superior não precisa apenas manipular arrays, pois há muitos casos de uso para retornar uma função de outra função. Function.prototype.bind é um desses exemplos em JavaScript.

Map

Digamos que temos um array de nomes que precisamos transformar cada string para maiúsculas.

const names = ['irish', 'daisy', 'anna'];

A forma imperativa será a seguinte:

const transformNamesToUppercase = function (names) { const results = []; for (let i = 0; i < names.length; i++) { results.push(names[i].toUpperCase()); } return results; }; transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']

Usar .map(transformerFn) torna o código mais curto e mais declarativo.

const transformNamesToUppercase = function (names) { return names.map((name) => name.toUpperCase()); }; transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']

Você pode dar um exemplo de desestruturação de um objeto ou um array?

Desestruturação é uma expressão disponível no ES6 que permite uma forma sucinta e conveniente de extrair valores de Objetos ou Arrays e colocá-los em variáveis distintas.

Desestruturação de array

// Atribuição de variável. const foo = ['one', 'two', 'three']; const [one, two, three] = foo; console.log(one); // "one" console.log(two); // "two" console.log(three); // "three"
// Troca de variáveis let a = 1; let b = 3; [a, b] = [b, a]; console.log(a); // 3 console.log(b); // 1

Desestruturação de objeto

// Atribuição de variável. const o = { p: 42, q: true }; const { p, q } = o; console.log(p); // 42 console.log(q); // true

ES6 Template Literals oferecem muita flexibilidade na geração de strings, você pode dar um exemplo?

Literais de template ajudam a simplificar a interpolação de strings, ou a incluir variáveis em uma string. Antes do ES2015, era comum fazer algo assim:

var person = { name: 'Tyler', age: 28 }; console.log( 'Olá, meu nome é ' + person.name + ' e eu tenho ' + person.age + ' anos!', ); // 'Olá, meu nome é Tyler e eu tenho 28 anos!'

Com literais de template, você agora pode criar a mesma saída assim:

const person = { name: 'Tyler', age: 28 }; console.log(`Olá, meu nome é ${person.name} e eu tenho ${person.age} anos!`); // 'Olá, meu nome é Tyler e eu tenho 28 anos!'

Observe que você usa crases, não aspas, para indicar que está usando um literal de template e que pode inserir expressões dentro dos espaços reservados ${}.

Um segundo caso de uso útil é na criação de strings de várias linhas. Antes do ES2015, você poderia criar uma string de várias linhas assim:

console.log('Esta é a linha um.\nEsta é a linha dois.'); // Esta é a linha um. // Esta é a linha dois.

Ou, se você quisesse dividi-la em várias linhas em seu código para não ter que rolar para a direita em seu editor de texto para ler uma string longa, você também poderia escrevê-la assim:

console.log('Esta é a linha um.\n' + 'Esta é a linha dois.'); // Esta é a linha um. // Esta é a linha dois.

Os literais de template, no entanto, preservam qualquer espaçamento que você adicionar a eles. Por exemplo, para criar a mesma saída de várias linhas que criamos acima, você pode simplesmente fazer:

console.log(`Esta é a linha um. Esta é a linha dois.`); // Esta é a linha um. // Esta é a linha dois.

Outro caso de uso de literais de template seria usar como um substituto para bibliotecas de templating para interpolações de variáveis simples:

const person = { name: 'Tyler', age: 28 }; document.body.innerHTML = ` <div> <p>Nome: ${person.name}</p> <p>Idade: ${person.age}</p> </div> `;

Observe que seu código pode ser suscetível a XSS usando .innerHTML. Sanitize seus dados antes de exibi-los se eles vierem de um usuário!

Você pode dar um exemplo de uma função de currying e por que essa sintaxe oferece uma vantagem?

Currying é um padrão em que uma função com mais de um parâmetro é dividida em várias funções que, quando chamadas em série, acumularão todos os parâmetros necessários um de cada vez. Essa técnica pode ser útil para tornar o código escrito em um estilo funcional mais fácil de ler e compor. É importante notar que, para uma função ser curried, ela precisa começar como uma única função, para depois ser dividida em uma sequência de funções, cada uma aceitando um parâmetro.

function curry(fn) { if (fn.length === 0) { return fn; } function _curried(depth, args) { return function (newArgument) { if (depth - 1 === 0) { return fn(...args, newArgument); } return _curried(depth - 1, [...args, newArgument]); }; } return _curried(fn.length, []); } function add(a, b) { return a + b; } var curriedAdd = curry(add); var addFive = curriedAdd(5); var result = [0, 1, 2, 3, 4, 5].map(addFive); // [5, 6, 7, 8, 9, 10]

Quais são os benefícios de usar a sintaxe de espalhamento (spread syntax) e como ela difere da sintaxe de descanso (rest syntax)?

A sintaxe de espalhamento do ES6 é muito útil ao codificar em um paradigma funcional, pois podemos facilmente criar cópias de arrays ou objetos sem recorrer a Object.create, slice ou uma função de biblioteca. Esse recurso de linguagem é frequentemente usado em projetos Redux e RxJS.

function putDookieInAnyArray(arr) { return [...arr, 'dookie']; } const result = putDookieInAnyArray(['Eu', 'realmente', 'não', 'gosto']); // ["Eu", "realmente", "não", "gosto", "dookie"] const person = { name: 'Todd', age: 29, }; const copyOfTodd = { ...person };

A sintaxe de descanso do ES6 oferece um atalho para incluir um número arbitrário de argumentos a serem passados para uma função. É como o inverso da sintaxe de espalhamento, pegando dados e colocando-os em um array em vez de descompactar um array de dados, e funciona em argumentos de função, bem como em atribuições de desestruturação de array e objeto.

function addFiveToABunchOfNumbers(...numbers) { return numbers.map((x) => x + 5); } const result = addFiveToABunchOfNumbers(4, 5, 6, 7, 8, 9, 10); // [9, 10, 11, 12, 13, 14, 15] const [a, b, ...rest] = [1, 2, 3, 4]; // a: 1, b: 2, rest: [3, 4] const { e, f, ...others } = { e: 1, f: 2, g: 3, h: 4, }; // e: 1, f: 2, others: { g: 3, h: 4 }

Como você pode compartilhar código entre arquivos?

Isso depende do ambiente JavaScript.

No cliente (ambiente de navegador), desde que as variáveis/funções sejam declaradas no escopo global (window), todos os scripts podem se referir a elas. Alternativamente, adote a Definição de Módulo Assíncrona (AMD) via RequireJS para uma abordagem mais modular.

No servidor (Node.js), a forma comum tem sido usar CommonJS. Cada arquivo é tratado como um módulo e pode exportar variáveis e funções anexando-as ao objeto module.exports.

O ES2015 define uma sintaxe de módulo que visa substituir tanto AMD quanto CommonJS. Isso será eventualmente suportado em ambientes de navegador e Node.

Por que você gostaria de criar membros de classe estáticos?

Membros de classe estáticos (propriedades/métodos) não estão vinculados a uma instância específica de uma classe e têm o mesmo valor independentemente de qual instância está se referindo a ele. Propriedades estáticas são tipicamente variáveis de configuração e métodos estáticos são geralmente funções utilitárias puras que não dependem do estado da instância.