17 perguntas de Javascript para entrevistas
undefined: valor primitivo utilizado quando uma variável não teve valor atribuído
null: valor primitivo que representa a ausência intencional de um valor de objeto
Antes de entender as diferenças entre undefined
e null
devemos entender as semelhanças entre eles.
- Eles pertencem aos 7 tipos primitivos do JavaScript .
let primitiveTypes = ['string','number','null','undefined','boolean','symbol','bigint']
- Eles são valores falsos. Valores avaliados como
falso
ao convertê-lo em booleano, usando:Boolean(value)
ou!!value
.
console.log('!!null: ',!!null) //logs falseconsole.log('!!undefined: ',!!undefined) //logs falseconsole.log('Boolean(null): ',Boolean(null)) //logs falseconsole.log('Boolean(undefined): ',Boolean(undefined)) //logs false
Ok, vamos falar sobre as diferenças:
undefined
é o valor padrão de uma variável que não recebeu um valor específico. Ou uma função que não tem valor de retorno explícito ex.console.log(1)
. Ou uma propriedade que não existe em um objeto. Exemplo:
let _thisIsUndefinedconsole.log('_thisIsUndefined', _thisIsUndefined) //logs undefinedconst doNothing = () => {}console.log(doNothing()) //logs undefinedconst someObj = {a : "ay",b : "bee",c : "si"}console.log(someObj["d"]) //logs undefined
null
é "um valor que não representa nada", mas que teve uma atribuição!null
é um valor que foi definido explicitamente para uma variável.
Neste exemplo, obtemos um valor de null
quando o método fs.readFile
não gera um erro (por baixo dos panos, alguém atribuiu o valor null a variável).
fs.readFile('path/to/file', (e,data) => {console.log(e) //it logs null when no error occurredif(e){console.log(e)}console.log(data)})
Ao compararmos null
e undefined
obtermos true
ao usar ==
(estranho né?)
e obtemos false
ao usar ===
. Vou explicar a diferença em breve.
console.log(null == undefined) // logs trueconsole.log(null === undefined) // logs false
O operador &&
ou Logical AND encontra a primeira expressão falsa em seus operandos e a retorna e, se não encontrar nenhuma expressão falsa, retorna a última expressão.
console.log(false && 1 && []) //logs falseconsole.log(" " && true && 5) //logs 5
Outra forma de usar é a verificação de curto-circuito
, para evitar trabalho desnecessário, ele automaticamente NÃO
executa as próximas instruções caso o resultado da primeira seja falso. Exemplo:
Repare no catch
, ele fecha a conexão do banco:
Usando if:
const router: Router = Router()router.get('/endpoint', (req: Request, res: Response) => {let conMobile: PoolConnectiontry {//do some db operations} catch (e) {if (conMobile) {conMobile.release()}}})
Usando o operador && (mais elegante):
const router: Router = Router()router.get('/endpoint', (req: Request, res: Response) => {let conMobile: PoolConnectiontry {//do some db operations} catch (e) {conMobile && conMobile.release()}})
O operador ||
ou Logical OR encontra a primeira expressão verdadeira em seus operandos e a retorna. Isso também emprega curto-circuito para evitar trabalho desnecessário.
console.log(null || 1 || undefined) // logga 1, nem chega a executar o resto
Ele inicializava valores padrões antes do ES6 chegar.
function logName(name) {var n = name || "Mark" // atribui a variável Mark caso não tenha nomeconsole.log(n)}logName() //logs "Mark"
Exemplo no ES6:
function logName(name = 'Mark') { // atribui a variável Mark caso não tenha nomevar n = nameconsole.log(n)}logName() //logs "Mark"
De acordo com a documentação da MDN, +
é a maneira mais rápida de converter uma string em um número porque ela não executa nenhuma operação no valor se já for um número.
Exemplo:
console.log(typeof '123') // stringconsole.log(typeof +'123') // number
O DOM (Document Object Model) é uma API para documentos HTML e XML . Ele fornece uma representação estrutural do documento, permitindo modificar o conteúdo e a apresentação visual usando uma linguagem de script como JavaScript.
Quando o navegador lê ( analisa ) nosso documento HTML pela primeira vez, ele cria um objetão de vários nós, uma estrutura de árvore que é modelada a partir do documento HTML que você escreveu, este é o DOM:
O DOM ele facilita e organiza a ordem dos elementos visuais na árvore, isso permite com que a gente modifique exatamente qualquer carinha que a gente quiser que esteja dentro dessa árvore.
Imagina que temos esse HTML simplão aqui:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document Object Model</title></head><body><div><p><span></span></p><label></label><input></div></body></html>
O equivalente DOM seria essa bagaça aqui:
No Javascript
a gente tem acesso a essa árvore através do objeto document
. Ele nos fornece muitos métodos que podemos selecionar e usar elementos para atualizar o seu conteúdo e algumas outras funcionalidades.
Caso queira executar o exemplo, use o codepen.io
Imagina o seguinte HTML:
<div id="avo"><div id="pai"><div id="filhao">Filhao</div></div></div>
Se a gente botar um evento de clique no #filhao
e no #avo
qual executaria primeiro?
document.getElementById("avo").addEventListener("click", function (event) {console.log("#avo clicado")})document.getElementById("filhao").addEventListener("click", function (event) {console.log("#filhao clicado")})
A propagação de eventos ocorre no DOM inteiro, e ele pode acontecer de baixo pra cima ou de cima pra baixo, não entendi, como assim?
O evento não vai acontecer magicamente só no seu botão, imagina que tem um mensageiro maluco que vai de nó em nó tentando avisar "mano, o #um foi clicado, ai vai pro próximo e fala a mesma coisa: mano, o #um foi clicado" até chegar em todos os nós, só que esse doidinho pode começar a fazer essa checagem a partir do:
botão -> em direção a window (Bubbling)
ou da
window -> em direção ao botão. (Capturing)
Exemplo:
A Propagação de Eventos possui três fases.
- Fase de captura - o evento começa a partir de
window
então desce para todos os elementos até atingir o elemento de destino. - Fase de destino - o evento atingiu o elemento de destino.
- Fase de Bubbling - o evento borbulha do elemento alvo e depois sobe todos os elementos até atingir o
window
.
Ainda não entendeu? Calma, acesse esses materiais:
O método event.preventDefault()
evita o comportamento padrão de um elemento.
Se usado em um form
, ele impede o envio (submit).
Se usado em um anchor
(<a>
), ele impede a navegação.
Se usado em um contextmenu
, impede a exibição.
Enquanto o event.stopPropagation()
interrompe a propagação de um evento ou impede a ocorrência do evento na fase de bubbling ou capturing (veja aula anterior).
Como saber se o event.preventDefault()
foi usado em um elemento?
Podemos usar a propriedade event.defaultPrevented
no objeto de evento, ele retorna um boolean
indicando se o event.preventDefault()
foi chamado em um elemento específico.
Exemplinho da massa: Caso queira executar o exemplo, use o codepen.io
HTML:
<p><a id="link1" href="#link1">Esse sim vai funcionar pq a gente não fez nada nele.</a></p><p><a id="link2" href="#link2">Tenta ir pro link 2</a> (ele não vai ir pq a gente bloqueou)</p><p id="log"></p>
JS:
function stopLink(event) {event.preventDefault();}function logClick(event) {const log = document.getElementById('log');if (event.target.tagName === 'A') {if (event.defaultPrevented) {log.innerText = 'Bloqueadão no baguio hein pai slc!\n' + log.innerText;}else {log.innerText = 'Esse sim pode!...\n' + log.innerText;}}}const a = document.getElementById('link2');a.addEventListener('click', stopLink);document.addEventListener('click', logClick);
Ainda não entendeu? Calma, acesse esses materiais:
O event.target é o elemento no qual o evento ocorreu ou o elemento que acionou o evento.
Exemplo de HTML:
Caso queira executar o exemplo, use o codepen.io
<div onclick="clickFunc(event)" style="text-align: centermargin:15pxborder:1px solid redborder-radius:3px"><div style="margin: 25px border:1px solid royalblueborder-radius:3px"><div style="margin:25pxborder:1px solid skyblueborder-radius:3px"><button style="margin:10px">Button</button></div></div></div>
Exemplo de JavaScript.
function clickFunc(event) {console.log(event.target) // botão//console.log(event.currentTarget) // div}
Nesse caso ele aciona no BOTÃO e não na DIV, por mais que tenhamos configurado o evento na div em si, quem é o target é o cara que foi acionado no evento.
currentTarget ?
O event.currentTarget é o elemento no qual anexamos explicitamente o manipulador de eventos (div nesse caso).
Mesmo exemplo, só vamos mudar no JS para usar o currentTarget
:
function clickFunc(event) {// console.log(event.target) // botãoconsole.log(event.currentTarget) // div}
Nesse caso ele vai apontar pra div (onde configuramos a ação) e não pro botão.
Caso queira executar os exemplos, use o codepen.io
A diferença entre ==
(abstrata) e ===
(estrita) é que 0 ==
compara por valor APÓS a coerção e ===
por valor e tipo sem coerção.
Vamos nos aprofundar no ==
. Mas, primeiro vamos falar sobre coerção .
Javascript não tem tipagem forte, então ele sempre vai tentar fazer a conversão das variáveis pra fazer determinadas operações, isso é chamado de coerção que é o processo de converter um valor para outro tipo.
tá ligado quando você usa o if(algumObjeto)
? por baixo dos panos o javascript converte esse maluco pra boolean e consegue dizer se ele existe ou não.
Voltando ao ==
ele faz essa coerção implícita que acabei de explicar, e para fazer isso ele segue um monte de regra maluca que depende de várias situações, que até o Brendan Eich tem dúvidas sobre isso então não se preocupe se você não entender de primeira.
SHOW ME THE CODE:
console.log('true == \'true\': ', true == 'true') // aqui vai dar trueconsole.log('true == \'1\': ', true == '1') // aqui vai dar trueconsole.log('true == true): ', true == true) // aqui vai dar trueconsole.log('[===] true === \'true\': ', true === 'true') // aqui vai dar falseconsole.log('[===]true === \'1\': ', true === '1') // aqui vai dar falseconsole.log('[===]true === true): ', true === true) // aqui vai dar true
Suponha que tenhamos que comparar x == y
.
- Se
x
ey
tiver o mesmo tipo. Em seguida, ele os compara com o operador===
. - Se
x
énull
ey
éundefined
então retornetrue
. - Se
x
éundefined
ey
énull
então retornetrue
. - Se
x
é do tiponumber
ey
é do tipostring
retornex == toNumber(y)
. - Se
x
é do tipostring
ey
é do tiponumber
retornetoNumber(x) == y
. - Se
x
for do tipoboolean
retornetoNumber(x) == y
. - Se
y
for do tipoboolean
retornex == toNumber(y)
. - Se
x
é um dos tipos (string
,symbol
ounumber
) ey
é o tipoobject
então retornex == toPrimitive(y)
. - Se
x
for umobject
ex
for umstring
,symbol
Então retornetoPrimitive(x) == y
. - Senão, retorne
false
.
Nota: toPrimitive
usa primeiro o valueOf
e depois o toString
nos objetos para obter o valor primitivo desse objeto.
Vamos dar exemplos.
x | y | x == y |
---|---|---|
5 | 5 | true |
1 | '1' | true |
null | undefined | true |
0 | false | true |
'1,2' | [1,2] | true |
'[object Object]' | {} | true |
Todos esses exemplos retornam true
.
O primeiro exemplo vai para a condição um porque x
e y
tem o mesmo tipo e valor.
O segundo exemplo vai para a condição quatro y
é convertida em number
antes de comparar.
O terceiro exemplo vai para a condição dois.
O quarto exemplo vai para a condição sete porque y
é boolean
.
O quinto exemplo vai para a condição oito . O array é convertido em string
usando o toString()
que retorna 1,2
.
O último exemplo vai para a condição dez . O objeto é convertido em um string
usando o toString()
que retorna [object Object]
.
x | y | x === y |
---|---|---|
5 | 5 | true |
1 | '1' | false |
null | undefined | false |
0 | false | false |
'1,2' | [1,2] | false |
'[object Object]' | {} | false |
Se usarmos o operador ===
, todas as comparações, com exceção do primeiro exemplo, retornarão false
, porque não têm o mesmo tipo, enquanto o primeiro exemplo retornará true
porque os dois têm o mesmo tipo e valor.
Ainda não entendeu? Calma, nem quem fez o JS entende isso direito (brinks) mas aqui estão alguns artigos legais pra tentar ao menos entender o básico disto:
Sempre olhe a documentação oficial.
Excelente artigo no Medium, falando a respeito.
Consulte a tabela de igualdade do JS
Exemplos de bizarrices no Javascript.
Outros exemplos de bizarrice.
Artigo interessante do Hackernoon.
Suponha que temos um exemplo abaixo.
let a = { a: 1 }let b = { a: 1 }let c = aconsole.log(a === b) // logga false mesmo que tenha a mesma propriedadeconsole.log(a === c) // logga true hmmmmmmm
JavaScript compara objetos e primitivos de maneira diferente.
Nas primitivas, as compara por valor enquanto nos objetos por referência ou pelo endereço na memória em que a variável está armazenada.
É por isso que o primeiro log
retorna false
e o segundo log
retorna true
. a
e c
tem a mesma referência e a
e b
não.
Por isso se você quiser comparar objetos use o stringify por exemplo:
function jsonEqual(object1, object2) {return JSON.stringify(object1) === JSON.stringify(object2);}jsonEqual(a, b) // true
O operador NÃO-duplo (!!) força a conversão do valor um booleano EXPLICITAMENTE, basicamente, é uma maneira elegante de converter um valor em um booleano.
console.log('!!null: ', !!null) //logs falseconsole.log('!!undefined: ', !!undefined) //logs falseconsole.log('!!\'\': ', !!'') //logs falseconsole.log('!!0: ', !!0) //logs falseconsole.log('!!NaN: ', !!NaN) //logs falseconsole.log('!!\' \' : ', !!' ') //logs trueconsole.log('!!{}: ', !!{}) //logs trueconsole.log('!![]: ', !![]) //logs trueconsole.log('!!1: ', !!1) //logs trueconsole.log('!![].length: ', !![].length) //logs false
Podemos usar a vírgula ,
para executar várias expressões em uma linha. Ele executa da esquerda para a direita e retorna o valor do último item à direita.
let x = 5x = (x++ , x = addFive(x), x *= 2, x -= 5, x += 10)function addFive(num) {return num + 5}
O que desgraça essa budega faz?
x++ // x vira 1
x = addFive(x) // 1 + 5 = x vira 6
x = 2 // 7 2 = x vira 22
x -= 5 // x vira 17
x += 10 // 17 + 10 = x vira 27
Nunca seja a desgraça de programador que escreve um código xemelento desse.
Se eu fosse reescrever esse código eu deixaria assim:
let originalValue = 5const INCREMENTED_VALUE = originalValue++const INCREMENTED_VALUE_PLUS_FIVE = addFive(INCREMENTED_VALUE)const RESULT_VALUE_MULTIPLIED_BY_TWO = INCREMENTED_VALUE_PLUS_FIVE * 2const DECREMENTED_RESULT_BY_FIVE = RESULT_VALUE_MULTIPLIED_BY_TWO - 5const RESULT_ADDED_WITH_TEN = DECREMENTED_RESULT_BY_FIVE + 10originalValue = RESULT_ADDED_WITH_TENfunction addFive(num) {return num + 5}
Hoisting no Javascript é o termo usado para descrever a movimentação de variáveis e funções para o topo de seu escopo (global ou função), em relação ao local de onde a definimos.
Exemplo:
printName()function printName() {console.log('Paulo Luan é bonitão pakarai')}
Para entender Hoist , temos que entender o contexto de execução.
O contexto de execução é o "ambiente de código" atualmente em execução. O contexto de execução possui duas fases de compilação e execução.
Compilação - nesta fase esse maluco pega as vars
e functions
para cima, para que possamos referenciá-los mais tarde e receber os valores.
Execução - nesta fase, atribui valores às variáveis içadas anteriormente e executa ou invoca funções (métodos em objetos) .
Nota: só vale se for function
ou var
RAÍZ, os nutella do ES6 NÃO são HOIÇADOS (let, const, arrow function e o restante).
veja os exemplos:
console.log(y)var y = 1 // VAR BRABO DO RAÍZ DO RISCA FACA FUNCIONA
console.log(y)let y = 1 // NUTELLA NÃO FUNCIONA
console.log(greet("Paulo Luan Bonitão"))function greet(name){return 'Olá ' + name + '!'}
console.log(greet("Paulo Luan Bonitão"))var greet = (name) => { // NUTTELAreturn 'Olá ' + name + '!'}//console.log(greet("Paulo Luan Bonitão"))
Escopo em JavaScript é a área em que temos acesso válido a variáveis ou funções.
JavaScript tem três tipos de escopos: Escopo Global , Escopo da Função e Escopo do Bloco (ES6).
- Escopo Global - variáveis ou funções declaradas no espaço de nomes global estão no escopo global e, portanto, estão acessíveis em qualquer lugar em nosso código.
//global namespacevar g = "global"function globalFunc(){function innerFunc(){console.log(g) // G é global, então é acessível aqui}innerFunc()}
- Escopo da Função - variáveis, funções e parâmetros declarados em uma função são acessíveis dentro dessa função, mas não fora dela.
function myFavoriteFunc(a) {if (true) {var b = "Hello " + a}return b}myFavoriteFunc("World")console.log(a) // Throws a ReferenceError "a" is not definedconsole.log(b) // does not continue here
- Escopo do bloco - variáveis (
let
,const
) declaradas em um bloco{}
só podem ter acesso dentro dele.
function testBlock(){if(true) {let z = 5}console.log(z)}testBlock() // Throws a ReferenceError "z" is not defined
Escopo também é um conjunto de regras para encontrar variáveis. Se uma variável não existe no escopo atual ele sai olhando nos escopos acima no escopo externo, e se não existe mais uma vez, olha para cima novamente até atingir o escopo global se a variável existe ele usa, senão manda aquele errão brabo na tela e para de procurar. Isso é chamado de cadeia de escopo.
/* Scope ChainInside inner function perspectiveinner's scope -> outer's scope -> global's scope*///Global Scopevar variable1 = "Comrades"var variable2 = "Sayonara"function outer(){//outer's scopevar variable1 = "World"function inner(){//inner's scopevar variable2 = "Hello"console.log(variable2 + " " + variable1)}inner()}outer()// logs Hello World// because (variable2 = "Hello") and (variable1 = "World") are the nearest// variables inside inner's scope.
Closures é um tópico controverso. O que eu gostaria de ouvir como entrevistador seria algo como:
Closures significa que uma função interna sempre tem acesso as variáveis e aos parâmetros de sua função externa, mesmo após o retorno da função externa.
Cê ta ligado que dá pra criar função dentro de função né?
exemplo:
function OuterFunction () {var outerVariable = 1function InnerFunction () {console.log(outerVariable)}InnerFunction()}
No exemplo acima, InnerFunction() pode acessar a variável outerVariable
mesmo ela estando declarada no escopo de cima.
function OuterFunction () {var outerVariable = 100var parameters = JSON.stringify(arguments)function InnerFunction () {console.log(`[OUTER CLOSURE] OuterVariable continua viva no meu coração: ${outerVariable}`)console.log(`[OUTER CLOSURE] Parâmetros que mandaram para o Outer: ${parameters}`)console.log(`[INNER] Meus parâmetros: ${JSON.stringify(arguments)}`)}return InnerFunction}var innerFunc = OuterFunction(123, "vral")innerFunc()
Ele chama a OuterFunction
mas a única referência que você tem é a da InnerFunction
, mas MESMO ASSIM você continua tendo acesso as variáveis que foram passadas pelo parâmetro para a OuterFunction
bem como o que foi declarado nela que no caso é a outerVariable
Isso permite que ninguém externamente consiga mudar essas variáveis de dentro do escopo do OuterFunction, olha só:
var counter = (function () {var privateCounter = 0function changeBy (val) {privateCounter += val}return {increment: function () {changeBy(1)},decrement: function () {changeBy(-1)},value: function () {return privateCounter},}})()var original = counter.value()console.log("Contador Original: ", original)alert(counter.value()) // altera o valorcounter.increment()counter.increment()console.log("Contador Original: ", original) // continua a mesma bostaalert(counter.value())counter.decrement()alert(counter.value())console.log("Contador Original: ", original) // continua a mesma bosta
Explicação do Douglinhas (um dos principais devs de JS do mundo).
Tutorial gostosinho do TutorialsTeacher.
"use strict"
é um recurso do ES5 no JavaScript que transforma nosso código no Modo Estrito em funções ou scripts inteiros . O Modo Estrito nos ajuda a evitar erros no início de nosso código e adiciona restrições a ele.
Restrições que o Modo Estrito nos fornece.
- Atribuindo ou acessando uma variável que não é declarada.
function returnY(){"use strict"y = 123return y}
- Atribuir um valor a uma variável global somente leitura ou não gravável
"use strict"var NaN = NaNvar undefined = undefinedvar Infinity = "and beyond"
- Excluindo uma propriedade não excluída.
"use strict"const obj = {}Object.defineProperty(obj, 'x', {value : '1'})delete obj.x
- Nomes de parâmetros duplicados.
"use strict"function someFunc(a, b, b, c){}
- Criando variáveis com o uso da função eval .
"use strict"eval("var x = 1")console.log(x) //Throws a Reference Error x is not defined
- O valor padrão disso será
undefined
.
"use strict"function showMeThis(){return this}showMeThis() //returns undefined
Existem muito mais restrições no modo estrito que essas.
Errar é extremamente natural, a série de aulas "Javascript para entrevistas" não tiveram o engajamento que eu esperava e eu acabei mudando o foco para outros assuntos de maior interesse das pessoas que me seguem no Instagram.
Errar é um dos processos mais importantes do aprendizado, e eu sempre digo aqui na Reativa que eu tenho a pele em jogo, ou seja, eu aplico as coisas que falo na prática NA VIDA REAL.
Errar não é um problema, o grande segredo é identificá-lo quanto antes e ajustar as devidas mudanças para conseguir os resultados que você almeja, entenda:
Na sua carreira de programador, pode ser que o erro esteja na empresa que você trabalha que não te ajuda a crescer como gostaria, amigos que não te ajudam a ser um dev melhor, ambientes que não colaboram para o seu crescimento como pessoa, etc.
Estar atento a todas essas coisas e mudar rapidamente os rumos da sua vida, te ajudarão a testar novas hipóteses e chegar mais rapidamente aos bons resultados. Crescer na maioria das vezes é melhor que ganhar, porque significa que você está se colocando em situações cada vez mais difíceis em vez de simplesmente estagnar.
A única coisa que não pode faltar é o trabalho duro! 💪
Os conteúdos não vão parar, pelo contrário, já estou preparando novos conteúdos extremamente interessantes para colocar por aqui, aguardem! 🐿⚡️
Estude o artigo original: 70 Interview Questions