
Um novo habrapost em uma série de análises do recente campeonato. Os participantes da qualificação que escolheram a seção de front-end tiveram que resolver várias tarefas de complexidade muito diferente: a primeira (de acordo com nossas expectativas) levou 20 minutos, a última - cerca de uma hora. Testamos uma ampla variedade de habilidades de desenvolvedor de interface, incluindo a capacidade de entender uma área incomum.
A. Aniquilá-lo
Autores: Maxim Sysoev, Konstantin PetryaevA primeira tarefa é um aquecimento. Cada participante obteve uma das quatro opções para a tarefa, semelhantes entre si. Propusemos não apenas uma condição textual, mas também uma solução recursiva "ruim". Era necessário refazer o código (escrever um algoritmo ganancioso que produzisse a solução mais rápida), removendo a recursão e várias bobagens, como operações e cálculos desnecessários.
Condição
Você conseguiu um emprego em um laboratório para o estudo da antimatéria, onde eles conduzem várias experiências. Seu departamento está estudando os processos que ocorrem ao combinar matéria e antimatéria. Você precisa realizar uma série de experimentos com um certo número de moléculas.
O departamento vizinho desenvolveu um aparelho que transforma a matéria em antimatéria por um curto período de tempo. Será útil para você realizar experiências nas quais o seguinte algoritmo é usado:
- Encontramos 2 das moléculas mais pesadas.
- Transformamos um deles em antimatéria.
- Combine-os. Além disso, se o peso for o mesmo, eles serão aniquilados. Se o peso for diferente, obteremos uma nova molécula, cujo peso é igual à diferença de pesos das duas anteriores. A própria molécula resultante é matéria.
- Se sobrar uma molécula, você precisará descobrir o seu peso. Se houver muitas moléculas, retornamos à etapa 1.
Você precisa descobrir a molécula de qual peso permanecerá no final do experimento; esse conhecimento é necessário por cientistas de outro departamento.
O desenvolvedor anterior esboçou o código que estava envolvido nesses cálculos, mas o código não pode concluir os cálculos quando o experimento é realizado em um grande número de moléculas. Você precisa refinar o código para que ele funcione em um período de tempo razoável.
Código herdado para vocêComo entrada, você terá uma matriz com pesos moleculares. Como saída, você precisa retornar um número que indica o peso da última molécula. Se não houver moléculas restantes, será necessário retornar 0.
var findLatestWeight = function(weights, i = weights.length - 1) { const cur = weights.length - 1 === i; if (i === 0) return weights[0]; weights.sort((a, b) => a - b); weights[i - 1] = (weights[i] === weights[i-1]) ? 0 : weights[i] - weights[i-1]; return findLatestWeight(weights, i - 1); }
Exemplo e notasExemplo
Entrada: [2,7,4,1,8,1]
Saída: 1
Pegamos moléculas com um peso de 7 e 8, transformamos 7 em uma antimolécula e colidimos com uma molécula de peso 8. Permanece uma molécula de peso 1. Os pesos das restantes moléculas de aço [2,4,1,1,1]. Pegamos moléculas com um peso de 2 e 4, transformamos 2 em uma antimolécula e colidimos com uma molécula de peso 4. Permanece uma molécula de peso 2. Os pesos das restantes moléculas de aço [2,1,1,1]. Pegamos moléculas com um peso de 2 e 1, transformamos 1 em uma antimolécula e colidimos com uma molécula de peso 2. Permanece uma molécula de peso 1. Os pesos das restantes moléculas de aço [1,1,1]. Pegamos moléculas com o peso de 1 e 1, transformamos uma delas em antimolécula e colidimos com a segunda. Eles são aniquilados. Os pesos das moléculas restantes [1]. Uma molécula esquerda. O resultado é 1.
Anotações
Como solução, forneça um arquivo que exporte a versão corrigida da função findLatestWeight:
function findLatestWeight(weights) {
A solução será executada no Node.js 12.
Solução
A solução "ruim" fornecida tem vários problemas ao mesmo tempo. O primeiro é a recursão. Conforme declarado na condição, processaremos grandes matrizes de números, o que elimina imediatamente uma solução recursiva.
var findLatestWeight = function(weights) { let i = weights.length - 1; do { if (i === 0) return weights[0] || 0; weights.sort((a, b) => a - b); weights[i-1] = (weights[i]=== weights[i-1]) ? 0 : weights[i]-weights[i-1]; i--; } while (true); }
A expansão da recursão aqui é bastante simples, mas surge outro problema - há uma reclassificação constante (de menor para grande) e funciona com o final da matriz. Como resultado, obtemos uma diminuição no penúltimo elemento na matriz. Mas depois disso, não aparamos a matriz e, se uma matriz de um milhão de elementos foi passada para a função, então a classificaremos novamente até o final.
Uma opção para resolver esse problema é tentar aparar constantemente a matriz.
var findLatestWeight = function(weights) { let i = weights.length - 1; do { if (i === 0) return weights[0] || 0; weights.sort((a, b) => a - b); weights[i-1] = (weights[i]=== weights[i-1]) ? 0 : weights[i]-weights[i-1]; weights.length = i;
Nada mal, mas também precisamos nos livrar da classificação, que por si só é uma operação cara. Em geral, a qualquer momento, estaremos interessados nos 2 maiores membros da matriz. Ou seja, é uma busca por dois máximos, o que é feito em uma passagem de maneira bastante simples. Por conveniência, realizamos essa pesquisa em uma função separada.
const maximumTwo = (arr) => { let max1 = arr[0]; let max2 = arr[1]; let max1I = 0; let max2I = 1; for(let i = 2; i < arr.length; i++) { if (arr[i] > max1) { if (max1 > max2) { max2 = arr[i]; max2I = i; } else { max1 = arr[i]; max1I = i; } } else if (arr[i] > max2) { max2 = arr[i]; max2I = i; } } if (max1 > max2) return [max2, max1, max2I, max1I]; return [max1, max2, max1I, max2I]; };
E alteramos a função de pesquisa da seguinte maneira:
const fn = function(weights) { if (weights.length <= 1) { return weights[0]; } do { const [x, y, xI, yI] = maximumTwo(weights); if (x === 0) { return y; } weights[xI] = 0; weights[yI] = y - x; } while(true); };
Assim, sempre zeramos o menor dos dois elementos e transformamos o maior na diferença entre eles. Nós nos livramos da classificação e recebemos uma passagem linear.
Dos erros comuns que notamos, os participantes pegaram o elemento máximo, multiplicaram por -1 e o adicionaram à segunda maior pedra. O resultado é um número negativo, que foi usado no cálculo "como está". Além disso, a tarefa tem uma armadilha mental associada ao fato de que você pode tentar deixar pedras de peso único e calcular a diferença delas. No entanto, essa abordagem não fornece o resultado correto.
B. BEM
Autores: Eugene Mishchenko, Vladimir Grinenko tadatutaCondição
Layout Alexander está envolvido em muitos projetos usando a metodologia BEM. Ele até criou um plugin útil para seu IDE favorito, que permite escrever nomes de classes em uma notação abreviada e implantá-los ao máximo. Mas o problema é que, para cada projeto, as pessoas definem delimitadores diferentes entre o bloco, o elemento e o modificador (block__mod__val - elem, block - mod - val ___ elem) e cada vez que ele precisa editar isso manualmente em seu plugin. Ajude Alexander a escrever um módulo que determinará o separador para entidades com base na classe. A regra para delimitadores é um número arbitrário de caracteres (não letras). Exemplos de possíveis notações (modificadores para um bloco nos dados de entrada podem não ter valor):
block_mod__elem
Esclarecimentos:
- As aulas nos projetos são escritas apenas em letras minúsculas.
- Uma string com uma classe CSS válida é alimentada na entrada do módulo.
O módulo deve retornar uma resposta do formulário:
{ mod: "_",
O módulo deve ser emitido como um módulo commonJS:
module.exports = function(str) { }
Solução
A segunda tarefa levou cerca de 20 minutos. Com sua ajuda, queríamos testar o conhecimento de expressões regulares entre os participantes.
A partir da condição, aprendemos que a entrada para a função será uma string que contém uma classe CSS válida com restrições adicionais, na qual as seqüências de letras são separadas por sequências arbitrárias de caracteres não-letras. Nossa tarefa é encontrar separadores e entender sua semântica.
A primeira parte do nome da classe sempre será o nome do bloco. Esta é uma sequência de uma ou mais letras. Nós escrevemos a expressão regular correspondente: [az] +.
Precisamos de expressões semelhantes para procurar as partes restantes: o nome do modificador e seu valor, ou o nome do elemento com o modificador e valor correspondentes.
Para procurar delimitadores, precisamos de sequências sem letras, a expressão: [^ az] + é adequada.
Junte-o e defina os grupos cujos valores usaremos:
let [, mod, elem ] = str.match(/[az]+(?:([^az]+)[az]+(?:\1)?[az]+)([^az]+)[az]+(?:\2)?[az]+/);
Agora você precisa ter certeza de que definimos corretamente a semântica dos grupos encontrados. Você pode tirar vantagem do fato de que apenas um modificador pode atender duas vezes.
Escreveremos uma função que utilizará a string original e o separador encontrado para calcular o número de ocorrências:
const substringCount = (source, substr) => (source.match(new RegExp('[az]' + substr + '[az]', 'g')) || []).length;
Se o elem do delimitador ocorrer duas vezes e mod - uma vez, na verdade o oposto é verdadeiro. A decisão final:
module.exports = function(str) { let [, mod, elem ] = str.match(/[az]+(?:([^az]+)[az]+(?:\1)?[az]+)([^az]+)[az]+(?:\2)?[az]+/); const substringCount = (source, substr) => (source.match(new RegExp('[az]' + substr + '[az]', 'g')) || []).length; if (substringCount(str, elem) === 2 && substringCount(str, mod) === 1) { [mod, elem] = [elem, mod]; } return { mod, elem }; }
C. Fábrica de clones
Autores: Dmitry Andriyanov dima117 , Alexey GusevCondição
Fora da janela é 2319. As empresas clonam funcionários bem-sucedidos para executar tarefas complexas.
Na produção de clones, eles decidiram rotular novos “produtos” com uma tatuagem de código de barras no ombro - para distinguir os clones um do outro.
Ajude a equipe da fábrica a escrever uma função que desenhe um código de barras com informações sobre o clone.
Clone Information FormatAs informações sobre o clone são armazenadas da seguinte maneira:
type CloneInfo = { sex: string; id: string; name: string; }
Algoritmo de renderização de código de barrasOs códigos de barras usados na fábrica de clones são assim:

O código de barras tem um tamanho fixo - 148 por 156 pixels. Ao redor do perímetro do código de barras, há quadros em preto e branco de 3 pixels cada. Dentro dos quadros está o conteúdo do código de barras, composto por 18 linhas de 17 quadrados em preto ou branco por linha. O tamanho de cada quadrado é de 8 por 8 pixels.
Quadrados brancos no conteúdo codificam 0, preto - 1.
Algoritmo de geração de conteúdo de código de barrasNa interseção da primeira linha e da primeira coluna do conteúdo, é desenhado um quadrado que codifica o sexo do clone. O valor da fêmea é codificado por zero (branco), masculino por um (preto).
Além disso, uma linha do formulário <id> <name> é formada a partir dos campos id e name. O campo de nome é preenchido com espaços no final de até 26 caracteres.
A sequência resultante é convertida em uma matriz de bytes - cada caractere da sequência recebe o código ASCII correspondente (um número de 0 a 255).
Em seguida, cada elemento da matriz resultante é convertido em notação binária (oito caracteres 0 ou 1) e codificado por uma sequência de oito quadrados (0 - quartzo branco, 1 - quadrado preto). Os quadrados são desenhados no conteúdo do código de barras sequencialmente e linha por linha.
A última linha de conteúdo contém informações de controle.
Algoritmo de contagem de informações de controleCada quadrado na linha de informações de controle determina a paridade da soma dos valores de conteúdo na coluna correspondente. Se a soma de zeros e uns na coluna for par, um quadrado branco será desenhado nas informações de controle, caso contrário, um quadrado preto.
Formato e exemplos da soluçãoFormato da soluçãoA solução que você carrega deve conter a função renderBarcode:
function renderBarcode(cloneInfo, element) {
Código de barras:

Exemplo 2
Informações do Clone:
{ "sex": "female", "id": "0owrgqqwfw", "name": "Dazdraperma Petrovna" }
Código de barras:

Solução
Era necessário formar corretamente a representação binária dos dados, calcular a soma de verificação e desenhar esses dados no layout. Vamos tentar fazer o mais simples e fácil possível - sem otimizações de código.
Vamos começar com a representação binária. Primeiro, declare funções auxiliares:
Formamos a partir dos dados de origem uma string composta por zeros e uns:
let dataString = (cloneInfo.sex === 'female' ? '0' : '1') + cloneInfo.id.split('').map(charToByte).map(byteToString).join('') + cloneInfo.name.padEnd(26, ' ').split('').map(charToByte).map(byteToString).join('');
Em seguida, escreva o layout e os estilos para o nosso código de barras:
Renderize dados binários no layout:
dataString .split('') .forEach((bit) => { const bitDiv = document.createElement('div'); bitDiv.className = 'content__bit content__bit_' + (bit === '0' ? 'zero' : 'one'); contentDiv.appendChild(bitDiv); });
Resta calcular e exibir a soma de verificação. Isso pode ser feito assim:
for (let i = 0; i < 17; i++) {
D. Automatize
Autores: Vladimir Rusov, Dmitry KanatnikovEm cada uma das opções de qualificação, havia uma tarefa em que uma página HTML com uma tabela ou lista foi proposta como entrada. As tarefas desta série tinham uma lenda diferente, mas todas se resumiam ao fato de que você precisava trazer a página para um formato semelhante ao Markdown. Analisaremos a solução para um dos problemas.
Condição
No portal estadual de prestação de serviços, eles possibilitaram enviar um pedido de documentos de forma totalmente automática, para isso, você só precisa preencher uma tabela com dados pessoais.
Esses dados são então transferidos para verificação a várias autoridades, incluindo o Ministério da Administração Interna. Após o início dos testes, o Ministério da Administração Interna aceita dados no formato Markdown e os Serviços de Estado usam o formato HTML. Ajude-me a escrever um script para migrar um formato para outro, para que os caras comecem o mais rápido possível.
Você precisa escrever uma função que use uma tabela HTML como entrada e a converta em marcação semelhante ao Markdown.
Como solução para esta tarefa, envie o arquivo .js no qual a função da solução é declarada:
function solution(input) {
Formato de entrada / saída e notasFormato de entrada
A tabela HTML vem como uma sequência:
<table> <colgroup> <col align="right" /> <col /> <col align="center" /> </colgroup> <thead> <tr> <td>Command </td> <td>Description </td> <th>Is implemented </th> </tr> </thead> <tbody> <tr> <th>git status</th> <td>List all new or modified files</td> <th>Yes</th> </tr> <tr> <th>git diff</th> <td>Show file differences that haven't been staged</td> <td>No</td> </tr> </tbody> </table>
A tabela pode conter tags colgroup, thead e tbody em uma ordem fixa. Todas essas tags são opcionais, mas pelo menos thead ou tbody sempre estarão presentes.
- colgroup contém tags col que podem ter o atributo de alinhamento opcional com um dos três valores (left | center | right)
- rosca e tbody contêm 1 ou mais tr
- tr, por sua vez, contém td e th
- A tabela sempre terá pelo menos uma linha. - A linha sempre terá pelo menos uma célula. - Pelo menos um símbolo que não seja espaço em branco está sempre presente na célula.
- O número de th / td elementos nas linhas sempre coincide entre todas as linhas e com o número de elementos col no colgroup, se houver colgroup.
- Espaços e quebras de linha no HTML de origem podem ocorrer em qualquer lugar que não viole a validade do HTML.
Formato de saída
A saída deve ser uma linha com marcação Markdown:
| Command | Description | **Is implemented** |
| ---: | :--- | :---: |
| **git status** | List all new or modified files | **Yes** |
| **git diff** | Show file differences that haven't been staged | No |
- A primeira linha encontrada em uma tabela sempre deve se transformar em uma linha de cabeçalho na marcação Markdown.
- Todas as outras linhas vão para o corpo da tabela.
- O separador de cabeçalho é sempre exibido.
- O conteúdo de td é inserido como está, o conteúdo de th como ** negrito **.
- Sempre há um espaço entre o conteúdo da célula na marcação de remarcação e os delimitadores da célula (|).
- Os espaços nas bordas do conteúdo das tags td e th devem ser removidos.
- Quebras de linha no conteúdo da célula devem ser excluídas.
- Mais de um espaço em uma linha no conteúdo das células deve ser substituído por um espaço.
- Para o alinhamento nas células das colunas da tabela Markdown, a formatação do separador de cabeçalho é responsável:
| : --- | significa alinhamento esquerdo
| : ---: | significa alinhamento central
| ---: | significa alinhamento correto
Se não houver nenhum atributo de alinhamento especificado na tag col, o alinhamento deve ser definido à esquerda.
Anotações
- Para o feed de linha, você precisa usar o caractere \ n.
- A solução será testada em um ambiente de navegador
(Chrome 78) com acesso a objetos de documentos e janelas.
- Você pode usar sintaxe até
es2018, inclusive.
Solução
O problema é resolvido simplesmente atravessando a árvore DOM da tabela. O suporte para a árvore DOM é implementado no nível do navegador, é parte integrante da mesma, portanto não haverá problemas. Para resolver o problema, basta converter a árvore DOM do HTML para a marcação Markdown.
Depois de examinar os exemplos, você pode ver que a conversão é bastante simples. Abaixo está o código que é o corpo da função da solução (entrada).
Primeiro, precisamos converter a string de HTML para a árvore DOM:
const div = document.createElement('div'); div.innerHTML = input; const table = div.firstChild;
Depois de receber uma árvore DOM, podemos apenas passar por ela e processar dados de diferentes nós DOM. Para fazer isso, basta ignorar recursivamente a sequência de filhos de vários elementos DOM:
const processors = { 'colgroup': processColgroup, 'thead': processThead, 'tbody': processTbody, }; for (let child of table.children) { processors[child.tagName.toLowerCase()](child); }
Nas tags colgroup e col, estamos interessados em conhecer o alinhamento das colunas da tabela:
const alignments = []; const defaultAlign = 'left'; const processColgroup = (colgroup) => { alignments.push(...Array(...colgroup.children).map(col => { return col.align || defaultAlign; })); };
Nas etiquetas thead, tbody e tr, estamos interessados apenas em crianças:
const rows = []; const processThead = (thead) => { rows.push(...Array(...thead.children).map(processTr)); }; const processTbody = (tbody) => { rows.push(...Array(...tbody.children).map(processTr)); }; const processTr = (tr) => { return Array(...tr.children).map(processCell); };
É importante não esquecer que, por convenção, td e th são formatados de maneira diferente:
const processCell = (cell) => { const tag = cell.tagName.toLowerCase(); const content = clearString(cell.innerHTML); return { 'td': content, 'th': `**${content}**`, }[tag]; };
Para trabalhar com o conteúdo de teste do DOM, você deve atender aos requisitos descritos na condição:
const clearLineBreaks = (str) => str.replace(/\r?\n|\r/g, ''); const clearSpaces = (str) => str.replace(/\s+/g, ' '); const clearString = (str) => clearSpaces(clearLineBreaks(str)).trim();
Depois de percorrermos a árvore do DOM, a maior parte da nossa tabela foi gravada na matriz de linhas:
[
["Command","Description","**Is implemented**"],
["**git status**","List all new or modified files","**Yes**"],
["**git diff**","Show file differences that haven't been staged","No"]
]
As informações de alinhamento da coluna estavam na matriz de alinhamentos:
["right","left","center"]
É importante lembrar que as informações de alinhamento da coluna podem não estar na entrada:
const updateAlignments = () => { if (alignments.length > 0) return; alignments.push(...rows[0].map(x => defaultAlign)); }; updateAlignments();
Converta alinhamentos na forma final:
const alignmentsContents = alignments.map(align => { return { 'left': ' :--- ', 'center': ' :---: ', 'right': ' ---: ' }[align]; }); const delimiter = `|${alignmentsContents.join('|')}|`;
Exemplo de valor delimitador:
"| ---: | :--- | :---: |"
A etapa final será a formação de uma linha de Markdown contendo todos os dados lidos do HTML:
const lineEnd = '\n'; rows.forEach((row, i) => { if (i > 0) markdown += lineEnd; const mdRow = `| ${row.join(' | ')} |`; markdown += mdRow; if (i === 0) { markdown += lineEnd; markdown += delimiter; } }); return markdown;
A construção de retorno significa que todo o código acima era o corpo da função de solução (entrada). Como resultado dessa função, obtemos o código da tabela Markdown desejado mostrado no exemplo de saída da condição da tarefa.
E. Vírus pandêmico
Autores: Andrey Mokrousov, Ivan PetukhovA Organização Mundial da Saúde publicou um relatório sobre sinais de uma pandemia iminente de um novo vírus que ameaça desenvolvedores front-end. É sabido que o vírus não se manifesta até que o host veja o código JS contendo alguma expressão. Assim que a pessoa infectada vê essa expressão, ela perde sua capacidade de escrever código em JS e começa a escrever espontaneamente no Fortran.
O relatório menciona que o vírus é ativado observando o primeiro argumento da função passado pelo argumento para a chamada de função Zyn, ou seja, uma pessoa infectada não pode mostrar uma expressão como Zyn (função (a, b, c) {console.log (a)}).
Para não perder acidentalmente todo o front-end, a AST & Co decidiu verificar se o código contém a expressão acima. Ajude os engenheiros da empresa a fazer esse cheque.
Sobre o código da AST & Co, sabemos que:
- está escrito em ES3,
- o acesso às propriedades de um objeto é possível através de um ponto e entre colchetes (ab e a ['b']),
- parte da expressão pode ser armazenada em uma variável, mas nunca é passada para a função pelo parâmetro (a (x) - proibido),
- não há funções que retornem parte da expressão desejada,
— , ,
— (a[x], x — ),
— , . . var a = x; a = y; var a = b = 1.
Formato da solução
CommonJS-, , (ast) .
ast-, callback-, Zyn , .
module.exports = function (ast) { ... return [...]; }
Anotações
.
function traverse( ast, onNodeEnter = (node, scope) => {}, onNodeLeave = (node, scope) => {} ) { const rootScope = new Scope(ast); _inner(ast, rootScope); function resolveScope(astNode, currentScope) { let isFunctionExpression = ast.type === 'FunctionExpression', isFunctionDeclaration = ast.type === 'FunctionDeclaration'; if (!isFunctionExpression && !isFunctionDeclaration) {
Solução
.
— ES3
, . , .
— , (ab a['b'])
Zyn, Z['y'].n, Zy['n'] Z['y']['n'].
, (a(x) — )
, . , : var x = Zy; xn(...).
— , ,
— , ,
— , .. var a = x; a = y; var a = b = 1.
( ) , - .
— , (a[x], x — )
, : var x = 'y'; Z[x].n(...).
C :
1. , , .
2. , .
, , — . 2.
: Zyn(function(a, b, c){...}), — .
FunctionExpression — CallExpression, callee — MemberExpression. property — n, object ( MemberExpression object property y) — Z.
, — — . — Identifier , MemberExpression ObjectLiteral (xa var x = {a: ...} ).
+++ b/traverse.js @@ -120,3 +120,59 @@ Scope.prototype = { return this._vars.has(name) || (this._parent && this._parent.isDefined(name)); } }; + +module.exports = function (ast) { + var result = []; + + traverse(ast, (node, scope) => { + if (node.type !== 'CallExpression') { + return; + } + let args = node.arguments; + if (args.length !== 1 || + args[0].type !== 'FunctionExpression') { + return; + } + let callee = node.callee; + if (callee.type !== 'MemberExpression') { + return; + } + let property = callee.property, + object = callee.object; + if (property.name !== 'n') { + return; + } + if (object.type !== 'MemberExpression') { + return; + } + property = object.property; + object = object.object; + if (property.name !== 'y') { + return; + } + if (object.type !== 'Identifier' || + object.name !== 'Z') { + return; + } + + checkFunction(args[0]); + }); + + function checkFunction(ast) { + let firstArg = ast.params[0]; + if (!firstArg) { + return; + } + + traverse(ast.body, (node, scope) => { + if (node.type !== 'Identifier') { + return; + } + if (node.name === firstArg.name) { + result.push(node); + } + }); + } + + return result; +};
traverse , , MemberExpression ObjectProperty. :
--- a/traverse.js +++ b/traverse.js @@ -60,16 +60,16 @@ function traverse( * @param {object} astNode ast- * @param {Scope} scope ast- */ - function _inner(astNode, scope) { + function _inner(astNode, scope, parent) { if (Array.isArray(astNode)) { astNode.forEach(node => { /* . * , , */ - _inner(node, scope); + _inner(node, scope, parent); }); } else if (astNode && typeof astNode === 'object') { - onNodeEnter(astNode, scope); + onNodeEnter(astNode, scope, parent); const innerScope = resolveScope(astNode, scope), keys = Object.keys(astNode).filter(key => { @@ -80,10 +80,10 @@ function traverse( keys.forEach(key => { // - _inner(astNode[key], innerScope); + _inner(astNode[key], innerScope, astNode); }); - onNodeLeave(astNode, scope); + onNodeLeave(astNode, scope, parent); } } } @@ -164,10 +164,22 @@ module.exports = function (ast) { return; } - traverse(ast.body, (node, scope) => { + traverse(ast.body, (node, scope, parent) => { if (node.type !== 'Identifier') { return; } + if (!parent) { + return; + } + if (parent.type === 'MemberExpression' && + parent.computed === false && + parent.property === node) { + return; + } + if (parent.type === 'ObjectProperty' && + parent.key === node) { + return; + } if (node.name === firstArg.name) { result.push(node); }
. getPropName:
--- a/traverse.js +++ b/traverse.js @@ -121,6 +121,18 @@ Scope.prototype = { } }; +function getPropName(node) { + let prop = node.property; + + if (!node.computed) { + return prop.name; + } + + if (prop.type === 'StringLiteral') { + return prop.value; + } +} + module.exports = function (ast) { var result = []; @@ -137,17 +149,17 @@ module.exports = function (ast) { if (callee.type !== 'MemberExpression') { return; } - let property = callee.property, + let property = getPropName(callee), object = callee.object; - if (property.name !== 'n') { + if (property !== 'n') { return; } if (object.type !== 'MemberExpression') { return; } - property = object.property; + property = getPropName(object); object = object.object; - if (property.name !== 'y') { + if (property !== 'y') { return; } if (object.type !== 'Identifier' ||
: . . 1.
ScopeScope . , , traverse:
--- a/traverse.js +++ b/traverse.js @@ -1,3 +1,12 @@ +const scopeStorage = new Map(); + +function getScopeFor(ast, outerScope) { + if (!scopeStorage.has(ast)) { + scopeStorage.set(ast, new Scope(ast, outerScope)); + } + + return scopeStorage.get(ast); +} - add(name) { - this._vars.add(name); + add(name, value) { + this._vars.set(name, { + value: value, + scope: this + }); + }, + resolve(node) { + if (!node) { + return node; + } + if (node.type === 'Identifier') { + let value = this._vars.get(node.name); + if (value) { + return value; + } + value = (this._parent && this._parent.resolve(node)); + return value; + } }, /** * . @@ -136,6 +161,12 @@ function getPropName(node) { module.exports = function (ast) { var result = []; + traverse(ast, (node, scope) => { + if (node.type === 'VariableDeclarator') { + scope.add(node.id.name, node.init); + } + }); + traverse(ast, (node, scope) => { if (node.type !== 'CallExpression') { return;
Scope. , Scope . , Scope , :
--- a/traverse.js +++ b/traverse.js @@ -146,13 +146,17 @@ Scope.prototype = { } }; -function getPropName(node) { +function getPropName(node, scope) { let prop = node.property; if (!node.computed) { return prop.name; } + let resolved = scope.resolve(prop); + if (resolved) { + prop = resolved.value; + } if (prop.type === 'StringLiteral') { return prop.value; } @@ -177,22 +181,43 @@ module.exports = function (ast) { return; } let callee = node.callee; + + let resolved = scope.resolve(callee); + if (resolved) { + callee = resolved.value; + scope = resolved.scope; + } + if (callee.type !== 'MemberExpression') { return; } - let property = getPropName(callee), + let property = getPropName(callee, scope), object = callee.object; if (property !== 'n') { return; } + + resolved = scope.resolve(object); + if (resolved) { + object = resolved.value; + scope = resolved.scope; + } + if (object.type !== 'MemberExpression') { return; } - property = getPropName(object); + property = getPropName(object, scope); object = object.object; if (property !== 'y') { return; } + + resolved = scope.resolve(object); + if (resolved) { + object = resolved.value; + scope = resolved.scope; + } + if (object.type !== 'Identifier' || object.name !== 'Z') { return;
: . :
— , Z — , - .
— , , .
— , var a = 'x', b = a.
, .
--- a/traverse.js +++ b/traverse.js @@ -128,10 +128,23 @@ Scope.prototype = { } if (node.type === 'Identifier') { let value = this._vars.get(node.name); - if (value) { - return value; + if (!value) { + if (this._parent) { + value = this._parent.resolve(node); + } else { +
:
const scopeStorage = new Map(); function getScopeFor(ast, outerScope) { if (!scopeStorage.has(ast)) { scopeStorage.set(ast, new Scope(ast, outerScope)); } return scopeStorage.get(ast); } function traverse( ast, onNodeEnter = (node, scope) => {}, onNodeLeave = (node, scope) => {} ) { const rootScope = getScopeFor(ast); _inner(ast, rootScope); function resolveScope(ast, currentScope) { let isFunctionExpression = ast.type === 'FunctionExpression', isFunctionDeclaration = ast.type === 'FunctionDeclaration'; if (!isFunctionExpression && !isFunctionDeclaration) {
F. Framework-
: , collapsusAPI. — , . .
— . . , . !
. , . , , , ( ). , , 0 (0 , 0 , 0 ).
, , . JavaScript JS- Framework.
: , . ( , ). () . ( ).
0. , ( time) .
const ONE_SECOND_DEGREES = 6; const ONE_SECOND_FACTOR = 1 / Framework.SPEED * ONE_SECOND_DEGREES; class MyClock extends Framework.Clock { constructor() { super(); this.arrows.push(new Framework.Arrow("seconds", { color: "red" })); this.arrows.push(new Framework.Arrow("minutes", { weight: 3, length: 80 })); this.arrows.push(new Framework.Arrow("hours", { weight: 3, length: 60 })); this.buttons.push(new Framework.Button("A", () => { alert("A"); })); this.tick = 0; } onBeforeTick() { const [arrow] = this.arrows; this.tick++; arrow.rotateFactor = this.tick % 10 ? 0 : ONE_SECOND_FACTOR; console.log("before: " + arrow.pos); } onAfterTick() { const [arrow] = this.arrows; console.log("after: " + arrow.pos); } }
:
— — , ,
— ,
— , ; (100 ) ; , .
Solução
, -, « », . , , , .
: , . . , , .
:
const TPS = 1000 / Framework.INTERVAL;
// .
function getTarget(ticks, planet) { const { h, m, s } = planet;
, — rotateFactor. getRotateFactor, , , . :
1. ,
2. .
. .
function getRotateFactor(pos, target, forward = true) { let angle = target - pos;
, MAX_SPEED . getRotateFactor.
const MAX_FACTOR = Framework.MAX_SPEED / Framework.SPEED; function getRotateFactor(pos, target, forward = true) { let angle = target - pos; if (forward) { angle < 0 && (angle += 360); } else { Math.abs(angle) > 180 && (angle -= Math.sign(angle) * 360) } const factor = angle / Framework.SPEED;
:
buttonAHandler() {
, :
onBeforeTick() { const [sec, min, hour] = this.arrows; const time = ++this.ticks; const planet = this.planets[this.pos];
:
const TPS = 1000 / Framework.INTERVAL; const MAX_FACTOR = Framework.MAX_SPEED / Framework.SPEED; function getTarget(ticks, planet) { const { h, m, s } = planet; const ts = Math.floor(ticks / TPS);
. . , , , .
: , , , , (, , ).
Conclusão. . — , . .
, . , ( ) 18 .
:
—
ML-—
-—
-