Recentemente, o
SmartSpate decidiu coletar perguntas sobre
JavaScript e respondê-las. O material, cuja tradução publicamos, contém um pouco mais de duas dúzias de perguntas sobre JavaScript e as respostas para elas. A gama de tópicos abordados aqui é bastante ampla. Em particular, esses são recursos da linguagem, problemas encontrados pelos programadores ao escrever o código JS, funcionam no navegador e no Node.js.

Pergunta número 1. Herança do protótipo
Acostumei-me às aulas "clássicas", mas decidi aprender JavaScript. Tenho um problema ao entender o modelo do protótipo. Se possível, explique, na forma de modelos, a capacidade de criar "classes" em JavaScript, conte-nos sobre métodos e propriedades particulares e abertos de classes. Eu entendo que muito já foi escrito sobre isso e que, em JavaScript, os métodos e propriedades dos objetos estão, por padrão, disponíveis ao público, mas eu gostaria de entender tudo isso corretamente. Como a herança de protótipo funciona em JavaScript?
▍ Resposta
A herança clássica é muito semelhante à maneira como as pessoas herdam os genes de seus ancestrais. As pessoas têm algumas habilidades básicas comuns, como caminhar e conversar. Além disso, cada pessoa tem algumas peculiaridades. As pessoas não podem mudar o que pode ser chamado de "classe", mas podem mudar suas próprias "propriedades" dentro de certos limites. Ao mesmo tempo, avós, mães e pais não podem influenciar os genes de filhos ou netos no processo de suas vidas. Então, tudo está organizado na Terra, mas vamos imaginar outro planeta no qual os mecanismos de herança trabalhem de uma maneira especial. Digamos que alguns organismos capazes de mutações usem os mecanismos de "herança telepática" lá. Isso se expressa no fato de que eles podem alterar as informações genéticas de seus próprios descendentes no processo de sua vida.
Considere o exemplo da herança neste planeta estranho. O objeto Pai herda genes do objeto Avô, e o objeto Filho herda informações genéticas do Pai. Cada habitante deste planeta pode sofrer mutações e alterar livremente os genes de seus descendentes. Por exemplo, em "Avô", a pele tem uma cor verde. Este sinal é herdado pelo "Pai" e "Filho". De repente, "Avô" decide que ele está cansado de ser verde. Agora ele quer ser azul e mudar sua cor de pele (em termos de JS - altera o protótipo de sua classe), passando "telepaticamente" essa mutação para o "Pai" e o "Filho". Depois disso, o “Pai”, acreditando que o “Avô” havia sobrevivido da mente, decide mudar seus genes para que fique verde novamente (ou seja, ele muda seu próprio protótipo). Essas mudanças são "telepaticamente" transmitidas ao "Filho". Como resultado, o "Pai" e o "Filho" têm a pele verde novamente. Ao mesmo tempo, "Avô" ainda é azul. Agora, não importa o que ele faça com sua cor, isso não afetará mais ninguém. E tudo isso se deve ao fato de o "Pai" definir explicitamente a cor de sua pele em seu "protótipo", e o "Filho" herdar essa cor. Então o "Filho" pensa assim: "Eu ficarei preto. E que meus descendentes herdem cores de meu pai. Para fazer isso, ele muda sua própria propriedade (e não a propriedade de seu protótipo) de forma que sua propriedade afete sua cor, mas não seus descendentes. Expressamos tudo isso na forma de código:
var Grandfather = function () {};
Pergunta número 2. Criar objetos
Se você criar novas instâncias de objetos usando a
new
palavra-chave, como se proteger contra erros? É assim que eu costumo trabalhar:
- Eu sempre construo funções construtoras para que elas comecem com uma letra maiúscula.
- Verifico a exatidão da operação usando o construtor
this instanceof Function_Name
(tento não usar o construto do tipo this instanceof arguments.callee
por motivos de desempenho). - Essa abordagem é semelhante à anterior, mas a comparação é feita com
window
, porque não gosto de codificar os nomes das entidades e não preciso escrever código para ambientes que não sejam o navegador.
Qual é o modelo mais conveniente para criar objetos?
▍ Resposta
É melhor, ideologicamente e com base na familiaridade desse método, criar objetos usando a
new
palavra-chave. Nesse caso, as funções do construtor devem receber nomes começando com uma letra maiúscula.
Eu prefiro seguir as regras e não executar verificações adicionais nos construtores. Se o construtor é chamado sem
new
e o trabalho começa no escopo global, isso pode ser comparado com "auto-engano". Ao mesmo tempo, não recomendo que você lide com situações em designers nos quais elas são chamadas sem a
new
palavra-chave. Por exemplo, pode ser assim: se o construtor for chamado sem
new
, então, de qualquer maneira, um novo objeto será criado e retornado. Essa abordagem é ideologicamente incorreta e leva a erros.
Aqui está um exemplo de trabalho com um construtor.
var Obj = function () { "use strict"; this.pew = 100; };
É melhor não usar a
new
palavra-chave para métodos de fábrica e nos casos em que não há necessidade de um construtor, quando será mais conveniente criar um objeto usando um literal de objeto. Digamos que o código do construtor mostrado no exemplo a seguir seja explicitamente redundante:
Se, mesmo para criar um objeto pequeno, você ainda precisar de um construtor, é melhor fazer isso:
var Obj = function () { "use strict"; this.pew = 100; };
Aqui, como mostrado acima, devido ao fato de o construtor funcionar no modo estrito, ocorrerá um erro ao chamá-lo sem
new
.
Pergunta número 3. Intercepção de cliques do mouse
Como, usando JavaScript, para saber em qual botão do mouse é clicado?
▍ Resposta
Clicar nos botões do mouse gera eventos de
mousedown
mouse e mouse. Nesse caso, o evento
click
é gerado apenas pelo botão esquerdo do mouse. No manipulador de eventos, é necessário verificar o código localizado na propriedade
event.button
para descobrir qual botão está pressionado (0 - esquerda, 1 - meio, 2 - direita). No entanto, no IE, tudo parece um pouco diferente. Considere um exemplo:
var button = document.getElementById ('button'),
A biblioteca jQuery leva esse recurso do IE em consideração; portanto, ao usá-lo em qualquer navegador, basta verificar o valor da propriedade
event.which
em vez de mexer no
event.button
:
$('button').mousedown(function (event) { alert(['Left', 'Middle', 'Right'][event.which]); });
Pergunta número 4. Intercepção de pressionamentos de teclas nas teclas do teclado
É possível interceptar, usando JavaScript, pressionando as teclas de seta (em particular, pressionando as teclas Para baixo e Para cima), para que, depois de clicar nelas, o navegador não role a página? Se isso for possível, quais são os recursos para implementar isso em diferentes navegadores? Suponha que uma página seja exibida em uma página que não cabe inteiramente na tela. A movimentação pelas células desta tabela deve ser organizada usando as teclas de seta, e é necessário que o navegador não role a página quando você clicar nessas teclas.
▍ Resposta
Para implementar algo parecido com isto, primeiro você precisa desativar a resposta padrão do sistema para controlar ações. Por exemplo, as teclas de seta e a roda do mouse rolam a página; clicar com o botão direito do mouse na página exibe um menu de contexto; quando você clica no botão
submit
, a função
form.submit()
é
form.submit()
; quando você clica no campo de entrada, ele recebe o foco de entrada; quando você clica no link, o navegador carrega a página para a qual ela leva.
Isso pode ser feito de
maneiras diferentes . Por exemplo, assim:
window.addEventListener("keydown", function(e) {
A página seguinte normalmente não responde aos pressionamentos de tecla de seta.
Uma coisa importante a ser observada aqui. O
preventDefault()
antes que a ação padrão seja executada. Por exemplo, se você clicar em um campo para impedir que ele ganhe foco de entrada, será necessário travar o manipulador apropriado em um evento que esteja na cadeia de eventos antes da ação padrão. No nosso caso, este é um evento de
mousedown
:
$('input').bind ('mousedown', function (event) { event.preventDefault();
Quando você clica no campo de entrada, ocorrem os seguintes eventos - na sequência em que são mostrados aqui:
mousedown
focus
(antes disso, outro objeto que está perdendo o foco acionará um evento de blur
)mouseup
click
Se tentarmos impedir que o elemento obtenha foco de entrada, o uso de manipuladores de eventos para isso, começando com o manipulador de eventos
focus
, não nos ajudará.
Pergunta número 5. Parar animação GIF e tecla ESC
Como lidar com o problema de interromper a animação GIF ao pressionar a tecla ESC?
▍ Resposta
Aqui você pode usar a mesma abordagem que consideramos acima. Em alguns navegadores, pressionar a tecla ESC interrompe a animação GIF e o carregamento da página. Esse é o comportamento padrão deles e, para impedir que eles se comportem dessa maneira, o
preventDefault()
evento
preventDefault()
é útil para nós, como antes. O código da tecla ESC é 27.
Pergunta número 6. Parênteses em IIFE
Como a construção de dois parênteses é usada ao declarar uma Expressão de Função Invocada Imediatamente (IIFE)?
▍ Resposta
Os colchetes nessa situação permitem que o analisador entenda que existe uma função na frente deles que precisa ser executada. Mas ele também precisa entender o que são esses colchetes - o operador de agrupamento ou uma construção indicando a necessidade de chamar a função. Por exemplo, se usarmos dois colchetes, como mostrado abaixo,
SyntaxError
um erro
SyntaxError
:
function () {
Isso se deve ao fato de a função não ter um nome (você deve especificar seus nomes nas declarações da função).
Vamos tentar reescrever esse código, dando um nome à função:
function foo() {
Agora que a função tem um nome, essa construção, teoricamente, deve parecer bastante normal do ponto de vista do sistema. Mas o erro não desaparece, embora agora tenha a ver com o operador de agrupamento, dentro do qual não há expressão. Observe que, nesse caso, a instrução de agrupamento é seguida pelo operador de agrupamento, e não uma sequência de colchetes que informa ao sistema que a função anterior a ela deve ser chamada.
Geralmente o IIFE é projetado da seguinte maneira:
(function () {
Mas existem outras maneiras, cuja essência é de alguma forma indicar ao analisador que antes era apenas uma expressão funcional que precisa ser executada:
!function () {
IIFE são amplamente utilizados na programação JavaScript. Por exemplo, essa construção é usada no jQuery. Com sua ajuda, você pode criar fechamentos. De fato, estamos falando sobre o fato de que, usando o IIFE, o programador pode executar algum código no escopo local. Isso ajuda a proteger o escopo global da poluição e permite otimizar o acesso a variáveis globais. Tais projetos são bem minificados.
Pergunta número 7. Passando código em resposta a solicitações
O servidor, no processo de interação do AJAX com o cliente, no corpo da resposta, envia ao cliente uma sequência de
alert ('Boom !!!');
. O cliente aceita a resposta e executa esse código usando a função
eval()
. Como se chama? Afinal, o que está contido na resposta do servidor não é JSON, XML ou HTML. O que você pode dizer sobre a execução no cliente do código que vem do servidor na forma do corpo da resposta a uma determinada solicitação?
▍ Resposta
De fato, não existe um nome especial para esse esquema de interação cliente-servidor. E este é um esquema de interação do sistema que é fortemente desencorajado. Isso é tão ruim quanto armazenar o código PHP em um banco de dados e depois executá-lo usando métodos de linguagem apropriados. Mesmo se não levarmos em consideração considerações ideológicas, podemos dizer que essa arquitetura é extremamente inflexível; portanto, se o projeto em que é usada, será necessário, à medida que se desenvolve, mudar alguma coisa, isso não será fácil. Aqui vemos um exemplo de arquitetura de sistema ruim quando dados são misturados com elementos de código e interface. Para alterar algo nesse sistema, primeiro você precisa entender os meandros de sua arquitetura complexa e, depois de fazer as alterações, "confunde" tudo novamente. Não estou falando sobre reutilização de código.
Para simplificar o suporte ao código, você precisa buscar a maior separação possível de partes do sistema e reduzir o número de interdependências dessas partes. Para garantir uma conectividade ruim de partes do sistema, ou seja, para garantir que um fragmento do aplicativo possa ser extraído dele ou, com a menor complexidade, substituído por outro, você pode usar mecanismos de eventos ou soluções arquiteturais especiais, como MVC.
Pergunta número 8. Executando operações pesadas no encadeamento principal
Como organizar a execução de certos comandos que exigem muitos recursos em JavaScript e não "suspender" o script inteiro?
▍ Resposta
JavaScript é uma linguagem de thread único. O código das páginas da Web é executado no mesmo encadeamento e as transformações da árvore DOM são executadas. Existem também temporizadores. Cada vez que você executa algumas operações que consomem recursos (ciclos, chamadas para funções "pesadas"), isso leva a uma desaceleração na interface do usuário ou mesmo ao seu bloqueio completo. Se as operações executadas não tiverem uma carga particularmente grande no sistema, o impacto na interface será tão insignificante que os usuários simplesmente não perceberão. Para tornar a computação pesada fora do segmento principal, o conceito de trabalhadores da Web foi introduzido em JavaScript.
Se o uso de trabalhadores não for possível, você precisará otimizar os ciclos e as funções "pesadas". No livro "JavaScript. Otimização de desempenho ”Nicholas Zakas diz que o usuário não notará nada se o fluxo da interface do usuário estiver bloqueado por 100 ms ou menos.
A partir dessa idéia, podemos concluir que os cálculos com uso intensivo de recursos podem ser divididos em fragmentos, cuja implementação leva no máximo 100 ms, após o qual o encadeamento principal deve ser liberado.
Aqui está um código de exemplo do livro acima:
function timedProcessArray(items, process, callback) { var todo = items.concat();
A função
timedProcessArray()
bloqueia o encadeamento principal por 25 ms, executa uma sequência de ações e o libera por 25 ms, após o que esse processo é repetido.
Pergunta número 9. Sobre o redimensionamento da janela do navegador
Posso descobrir de alguma forma que o usuário terminou de redimensionar a janela do navegador?
▍ Resposta
Não há evento especial que permita que você descubra. Mas você pode descobrir se o usuário está redimensionando a janela usando o evento
onresize
. Este método, no entanto, não é muito preciso.
Aqui está um rascunho de código para resolver esse problema.
var time = 0, timerId, TIME_ADMISSION = 100;
Pergunta número 10. Abrindo novas janelas e guias do navegador
Como, usando o método
window.open()
, para abrir uma nova janela do navegador e não uma nova guia?
▍ Resposta
O comportamento exato do método
window.open()
depende do navegador. O Opera sempre abre novas abas (embora pareçam janelas), o Safari sempre abre janelas (embora esse comportamento possa ser alterado). O comportamento do Chrome, Firefox e Internet Explorer pode ser controlado.
Portanto, se um parâmetro adicional (posição da janela
window.open()
passado para o método
window.open()
, uma nova janela será aberta:
window.open('http://www.google.com', '_blank', 'toolbar=0,location=0,menubar=0');
Se apenas um link for passado para esse método, uma nova guia do navegador será aberta:
window.open('http://www.google.com');
Muitas vezes, você precisa abrir uma nova guia do navegador. Pode haver problemas com isso no navegador Safari. Por padrão (depende das configurações), o navegador, quando
window.open()
é chamado, abre uma nova janela. Mas se você clicar no link, enquanto pressiona as teclas
Ctrl + Shift/Meta + Shift
, uma nova guia será aberta (independentemente das configurações). No exemplo a seguir, simularemos o evento de
click
gerado quando as
Ctrl + Shift/Meta + Shift
são pressionadas:
function safariOpenWindowInNewTab (href) { var event = document.createEvent ('MouseEvents'), mac = (navigator.userAgent.indexOf ('Macintosh')> = 0);
Pergunta nº 11. Objetos de cópia profunda
Como organizar efetivamente a cópia profunda de objetos?
▍ Resposta
Se o objeto cuja cópia você deseja criar (vamos chamá-lo de
oldObject
) não for alterado, será mais eficaz fazer isso através de seu protótipo (isso é feito muito rapidamente):
function object(o) { function F() {} F.prototype = o; return new F(); } var newObject = object(oldObject);
Se você realmente precisar executar a operação de clonagem de um objeto, o caminho mais rápido será recursivamente, otimizando esse processo, passando por suas propriedades. Talvez este seja o algoritmo mais rápido para criar cópias profundas de objetos:
var cloner = { _clone: function _clone(obj) { if (obj instanceof Array) { var out = []; for (var i = 0, len = obj.length; i < len; i++) { var value = obj[i]; out[i] = (value !== null && typeof value === "object") ? _clone(value) : value; } } else { var out = {}; for (var key in obj) { if (obj.hasOwnProperty(key)) { var value = obj[key]; out[key] = (value !== null && typeof value === "object") ? _clone(value) : value; } } } return out; }, clone: function(it) { return this._clone({ it: it }).it; } }; var newObject = cloner.clone(oldObject);
Se você usar jQuery, poderá recorrer às seguintes construções:
Pergunta número 12. Destruidores de JavaScript
Como criar algo como um destruidor em JavaScript? Como gerenciar o ciclo de vida dos objetos?
▍ Resposta
Em JavaScript, um objeto será excluído da memória após a última referência a ele desaparecer:
var a = {z: 'z'}; var b = a; var c = a; delete az; delete a;
Usar algo como um “destruidor” no JavaScript resulta apenas na limpeza do conteúdo do objeto, mas não na exclusão da memória.
Pergunta número 13. Processamento de dados binários
É possível processar dados binários em JavaScript? E se sim, como?
▍ Resposta
Se você precisar trabalhar com dados binários em um aplicativo JavaScript, tente usar a biblioteca
Analisador Binário . Mas o código dela é um inferno. No ES6 +, há uma sugestão sobre o tipo
StructType
(é o mesmo que o apresentado em C ++ pelo tipo composto para a
struct
especificada). Esse tipo de dado é necessário para simplificar o trabalho com dados binários. Trabalhar com isso pode ser algo como isto:
const Point2D = new StructType({ x: uint32, y: uint32 }); const Color = new StructType({ r: uint8, g: uint8, b: uint8 }); const Pixel = new StructType({ point: Point2D, color: Color }); const Triangle = new ArrayType(Pixel, 3); let t = new Triangle([{ point: { x: 0, y: 0 }, color: { r: 255, g: 255, b: 255 } }, { point: { x: 5, y: 5 }, color: { r: 128, g: 0, b: 0 } }, { point: { x: 10, y: 0 }, color: { r: 0, g: 0, b: 128 } }]);
Pergunta número 14. Alterando variáveis em uma função de outra função
Como alterar variáveis localizadas em uma função de outra função?
▍ Resposta
Aqui você pode aplicar várias abordagens:
- Você pode usar o link para o contexto da função de seu interesse (a função
primer()
no exemplo a seguir) na função smth()
.
- Você pode passar uma função criada no contexto da função
primer()
para a função smth()
.
var primer = function () { var a, b, c, d, e = {}; smth (function () { a = 1; b = 2; c = 3; d = 4; }, e); alert ([a, b, c, d, e.pewpew]); }, smth = function (callback, e) { callback (); e.pewpew = "pewpew"; }; primer ();
- Anteriormente (antes do Firefox 3.6), era possível
__parent__
o contexto usando a propriedade __parent__
, mas já no Firefox 4 esse recurso foi removido.
Pergunta número 15. Trabalhar com funções
Diga-nos como as funções podem ser chamadas em JavaScript.
▍ Resposta
Suponho que não seja necessário falar sobre como chamar funções, métodos e construtores durante o trabalho normal com eles. Vamos falar sobre como usar os métodos
call()
e
apply()
.
Usando call () para configurar o construtor de um objeto
Converter objetos do tipo matriz em matrizes
Os objetos do tipo matriz são semelhantes aos do JavaScript, mas não são. Em particular, isso é expresso no fato de que esses objetos não possuem métodos de matrizes comuns. Entre esses objetos, por exemplo, podem ser observados os
arguments
objeto de funções tradicionais e os resultados do método
getElementsByTagName () .
Criando Objetos Wrapper
Essa técnica de usar
call()
e
apply()
permite criar objetos wrapper. , -
foo()
,
bar()
.
:
function bar () {console.log(arguments)}
Function.call.apply()
:
function foo() { Function.call.apply(bar, arguments); }
,
Function.call.apply()
, ,
foo()
,
bar
.
№16.
, , ?
▍
. , Firefox 3.6,
__parent__
, .
№17.
, , ,
eval()
?
▍
, . , :
№18.
, JavaScript, , ?
▍
JavaScript - « ». . . , :
$('#smth').click(function onSmthClick(event) { if (smth) {
— , . , 2 :
$('#smth'). click (function handler1 (event) { if (smth) {
№19.
JavaScript ? — , .
▍
click
«» DOM. (, , ).
// jQuery $(window).bind ('click', function (e) { console.log ('Clicked on', e.target); }); // $('#pewpew').delegate ('*', 'click', function(e) { console.log('Clicked on', e.target); }); // $('#pewpew').delegate('.pewpew', 'click', function (e) { console.log ('Clicked on element with .pewpew class name'); });
№20. XHR-
XHR- jQuery?
▍
, - :
function xhr(m, u, c, x) { with(new XMLHttpRequest) onreadystatechange = function (x) { readyState ^ 4 || c(x.target) }, open(m, u), send() }
- :
function xhr(m, u, c, x) { with(new(this.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP")) onreadystatechange = function (x) { readyState ^ 4 || c(x) }, open(m, u), send() }
:
xhr('get', '//google.com/favicon.ico', function (xhr) { console.dir(xhr) });
№21. -
(reflow) (repaint) -?
▍
requestAnimationFrame()
, , setInterval()
setTimeout()
. , , , , , . , JavaScript- CSS- SVG-. , , , , , , . , , .- float- ( ).
- DOM. , , , DOM ( ).
- — . ( , , ). Aqui está um exemplo:
// element.style.left = "150px;"; // ... element.style.color = "green"; // , , element.setAttribute ('style', 'color: green; left: 150px');
- ( ).
- — (
style.display = "none"
). , .
, -, . , , , , .
- .
- DOM- ( — ).
Document.querySelectorAll()
firstElementChild
.- ,
document.getElementsByTagName()
( , DOM, ).
№22. Node.js
Node.js, , ?
▍
, ( PHP Apache). , , . Node.js — . , ,
cluster . (master) - (worker). , .
№23. runInNewContext() Node.js
runInNewContext()
Node.js.
▍
. , ( Node.js- Nodester). - ,
runInNewContext()
. , «», . «» , ,
runInNewContext()
.
Sumário
JavaScript . , - , .
Caros leitores! , - , , JavaScript , ?
