Ответы пользователя по тегу JavaScript
  • Как посчитать количество элементов массива, имеющих определённое свойство?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Конечно, можно прямо посчитать количество интересующих нас элементов:

    const countWithKey = (arr, key) => arr.filter(n => key in n).length;
    
    console.log(countWithKey(arr, 'ключ'));

    Но можно решить задачу и в более общем виде.

    Например, просуммировав данные. Функция суммирования кроме данных получает также функцию, которая из отдельного элемента данных извлекает слагаемое, в данном случае - факт наличия нужного свойства:

    const sum = (data, val = n => n) =>
      Array.prototype.reduce.call(
        data,
        (acc, n) => acc + val(n),
        0
      );
    
    console.log(sum(arr, obj => obj.hasOwnProperty('ключ')));

    Кстати, что даёт более общий вид.
    Считать можно разные суммы. Как, например, обычных массивов из чисел (sum([ 1, 2, 3 ]) // 6), так и более сложные варианты. Например, есть массив, представляющий содержимое корзины с товарами (цена, количество), надо посчитать общую стоимость:

    const cart = [
      { price: 100, count: 5 },
      { price:  10, count: 6 },
      { price:   1, count: 7 },
    ];
    
    const total = sum(cart, item => item.price * item.count); // 567

    Или, считаем количество лайков за ответы на этот вопрос (можете открыть консоль и прямо там выполнить этот код):

    const likes = sum(document.querySelectorAll('.btn_like .btn__counter'), n => +n.innerText);


    Или, можно посчитать, сколько элементов набора данных относится к той или иной группе. Функция подсчёта помимо данных получает в качестве параметра функцию, которая принимает элемент данных, и возвращает группу, к которой он относится:

    function Counter(data, key = n => n) {
      const counted = new Map;
    
      for (const n of data) {
        const k = key(n);
        counted.set(k, (counted.get(k) ?? 0) + 1);
      }
    
      return k => counted.get(k) ?? 0;
    }
    
    const keyExists = Counter(arr, obj => Object.hasOwn(obj, 'ключ'));
    console.log(keyExists(true)); // смотрим, у скольких элементов массива ключ есть
    console.log(keyExists(false)); // и у скольких нет

    Аналогично суммированию, есть разные варианты применения.

    const str = 'hello, world!!';
    
    const chars = Counter(str);
    console.log(chars('h')); // 1
    console.log(chars('!')); // 2
    console.log(chars('x')); // 0

    const persons = [
      { name: 'Вася', birthday: new Date('1999-05-22') },
      { name: 'Маша', birthday: new Date('2004-03-06') },
      { name: 'Катя', birthday: new Date('1976-05-15') },
      { name: 'Петя', birthday: new Date('1987-04-18') },
      { name: 'Коля', birthday: new Date('2000-01-01') },
      { name: 'Дима', birthday: new Date('2003-05-09') },
      { name: 'Миша', birthday: new Date('1996-02-29') },
      { name: 'Таня', birthday: new Date('1981-03-12') },
      { name: 'Олег', birthday: new Date('1992-08-24') },
    ];
    
    const birthMonths = Counter(
      persons,
      ({ birthday }) => birthday.toLocaleString('ru-RU', { month: 'long' })
    );
    console.log(birthMonths('май')); // в мае родилось три человека
    console.log(birthMonths('март')); // в марте два
    console.log(birthMonths('октябрь')); // а в октябре никто

    function* naturalNumbers(n) {
      for (let i = 1; i <= n; i++) {
        yield i;
      }
    }
    
    const numLengths = Counter(naturalNumbers(100), num => `${num}`.length);
    console.log(numLengths(2)); // среди первых ста натуральных чисел - девяносто двухзначных
    console.log(numLengths(3)); // и одно трёхзначное
    console.log(numLengths(0)); // число из нуля знаков? - конечно же нет таких

    Ответ написан
    1 комментарий
  • Как правильно вывести древовидный объект в виде строки?

    0xD34F
    @0xD34F Куратор тега JavaScript
    function objToString(val, tabSize = 2, depth = 0, noIndent = false) {
      const indent = ' '.repeat(tabSize * depth);
    
      return (noIndent ? '' : indent) + (
        val instanceof Array
          ? `[\n${val.map(n => objToString(n, tabSize, depth + 1)).join(',\n')}\n${indent}]`
          : val instanceof Object
            ? `{\n${Object
                .entries(val)
                .map(n => n.map((m, i) => objToString(m, tabSize, depth + 1, i)).join(': '))
                .join(',\n')}\n${indent}}`
            : typeof val === 'string'
              ? `"${val}"`
              : val
      );
    }
    
    
    console.log(objToString({
      numbers: [ 69, 187, 666 ],
      strings: [ 'hello, world!!', 'fuck the world', 'fuck everything' ],
      falsy_values: [ 0, '', NaN, false, null, undefined ],
      object: { xxx: true, yyy: Infinity, zzz: { '!&$': [ [ [ -1 ] ] ] } }
    }));
    Ответ написан
    Комментировать
  • Как заставить работать несколько граф с оценками?

    0xD34F
    @0xD34F Куратор тега JavaScript
    вторая не работает

    Работает. Только не независимо, а как часть первой. Надо не все .grade-item обрабатывать, а только те, у кого тот же родитель, что и у кликнутого:

    document.querySelector('.nav-student-new-lesson').addEventListener('click', e => {
      if (e.target.classList.contains('grade-item')) {
        const items = [...e.target.parentNode.children];
        const grade = 1 + items.indexOf(e.target);
        const color = [
          [ 5, 'rgba(150, 255, 0, 0.3)' ],
          [ 3, 'rgba(255, 150, 0, 0.3)' ],
          [ 1, 'rgba(255, 0, 0, 0.3)' ],
        ].find(n => n[0] <= grade)[1];
    
        items.forEach((n, i) => n.style.background = i < grade ? color : 'white');
      }
    });
    Ответ написан
    Комментировать
  • Как переключить подсветку на следующий элемент списка при нажатии стрелок вверх-вниз?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const listEl = document.querySelector('#list');
    const activeClass = 'active';
    let index = 0;
    
    next(0);
    
    document.addEventListener('keydown', function(e) {
      const step = ({
        ArrowDown: 1,
        ArrowUp: -1,
      })[e.key];
    
      if (step) {
        next(step);
      }
    });
    
    function next(step) {
      const elems = listEl.children;
      elems[index].classList.remove(activeClass);
      index = Math.max(0, Math.min(elems.length - 1, index + step));
      // или, если надо, чтобы при переходе от последнего к следующему элементу
      // активным становился первый, а при переходе от первого к предыдущему
      // активным становился последний
      // index = (index + step + elems.length) % elems.length;
      elems[index].classList.add(activeClass);
    }
    Ответ написан
    Комментировать
  • Как вернуть новый массив объектов только с уникальным id вложенного объекта?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Сделаем функцию, которая будет принимать следующие параметры:
    • массив, подлежащий уникализации
    • функцию, принимающую элемент массива и возвращающую значение, по которому осуществляется уникализация

    Чтобы можно было делать так: const result = unique(arr, n => n.user.id);.

    Какие тут возможны варианты:

    const unique = (arr, key) =>
      Object.values(Object.fromEntries(arr.map(n => [ key(n), n ])));
    
    // или, если в результирующий массив должны попадать те из "одинаковых" элементов,
    // что расположены в исходном массиве первыми
    
    const unique = (arr, key) =>
      Object.values(arr.reduce((acc, n) => (acc[key(n)] ??= n, acc), {}));
    
    // или, если надо также сохранять взаимное расположение элементов
    
    const unique = (arr, key) =>
      arr.filter(function(n) {
        const k = key(n);
        return !(this[k] = this.hasOwnProperty(k));
      }, {});

    Если значения, по которым осуществляется уникализация, могут, будучи различными, иметь одинаковый строковый эквивалент, то на помощь приходят Map и Set:

    const unique = (arr, key) =>
      Array.from(new Map(arr.map(n => [ key(n), n ])), n => n[1]);
    
    // или (в отличие от объекта, Map запоминает порядок вставки,
    //      так что тут взаимное расположение элементов сохраняется)
    
    const unique = (arr, key) =>
      [...arr.reduce((acc, n) => {
        const k = key(n);
        return acc.set(k, acc.get(k) ?? n);
      }, new Map).values()];
    
    // или
    
    const unique = (arr, key) =>
      arr.filter(function(n) {
        const k = key(n);
        return !this.has(k) && this.add(k);
      }, new Set);
    Ответ написан
    Комментировать
  • Как активировать select по нажатию на radio?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const radios = document.querySelectorAll('[name="radios"]');
    const selects = Array.from(radios, n => n.nextElementSibling);
    const onChange = e => selects.forEach(n => n.disabled = n !== e.target.nextElementSibling);
    radios.forEach(n => n.addEventListener('change', onChange));
    Ответ написан
  • Как создать массив чисел, которые увеличиваются с каждой итерацией?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const arithmeticProgression = ({ length, a1 = 0, d = 1 }) =>
      Array.from(
        { length },
        (n, i) => a1 + i * d
      );
    
    
    const arr = arithmeticProgression({
      length: 10,
      a1: 6,
      d: 3,
    });

    или

    function* arithmeticProgression(a1, d, length) {
      for (let i = 0; i < length; i++) {
        yield a1 + i * d;
      }
    }
    
    
    for (const n of arithmeticProgression(100, 10, 5)) {
      console.log(n);
    }
    
    console.log(Array.from(arithmeticProgression(10, -7, 10)));
    Ответ написан
    Комментировать
  • Как сделать object to HTML string?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const voidTags = [ 'input', 'img', 'br', 'hr', ещё какой-то тэг, и ещё, ... ];
    
    function createHTML(data) {
      const attrs = Object
        .entries(data.attrs ?? {})
        .map(n => `${n[0]}="${n[1]}"`)
        .join(' ');
    
      const startTag = `<${data.tagName}${attrs && (' ' + attrs)}>`;
    
      if (voidTags.includes(data.tagName)) {
        return startTag;
      }
    
      const children = (data.subTags ?? [])
        .map(createHTML)
        .join('');
    
      return `${startTag}${data.text ?? ''}${children}</${data.tagName}>`;
    }
    Ответ написан
    Комментировать
  • Как с помощью рекурсии преобразовать массив?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Не надо никакой рекурсии:

    arr.map((n, i, a) => a.slice(0, i + 1).join(''))
    // или
    arr.reduce((acc, n) => (acc.push((acc.at(-1) ?? '') + n), acc), [])

    Но, конечно, если очень хочется, то

    (function xxx(arr, str = '') {
      if (str.length === arr.length) {
        return [];
      }
    
      const newStr = str.concat(arr[str.length]);
      return [ newStr, ...xxx(arr, newStr) ];
    })(arr)
    Ответ написан
    1 комментарий
  • Как устранить затухание popup при клике на него?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Вызывайте fadeOut только после того, как убедитесь, что кликнут был кто надо:

    overlay.click(e => {
      if (e.target === e.delegateTarget) {
        overlay.fadeOut();
      }
    });
    
    // или
    
    overlay.click(function(e) {
      $(this).filter(e.target).fadeOut();
    });

    Или останавливайте всплытие кликов на вложенных элементах:

    overlay
      .click(() => overlay.fadeOut())
      .children()
      .click(e => e.stopPropagation());
    Ответ написан
    1 комментарий
  • Как запустить цикл each для каждого ключа json?

    0xD34F
    @0xD34F Куратор тега JavaScript
    $.each($.parseJSON(response), (k, v) => $(`.${k}`).text(v));

    или

    for (const [ k, v ] of Object.entries(JSON.parse(response))) {
      document.querySelector(`.${k}`).innerText = v;
    }
    Ответ написан
    1 комментарий
  • Как отсортировать массив в обратном порядке?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Ничего сортировать не надо. И обратный порядок не нужен. Вставляем сразу всё:

    document.querySelector('.listing').after(...arrayWords.map(n => {
      const p = document.createElement('p');
      p.innerHTML = n;
      return p;
    }));
    
    // или
    
    document.querySelector('.listing').insertAdjacentHTML('afterend', arrayWords
      .map(n => `<p>${n}</p>`)
      .join('')
    );

    А если не сразу всё, то всё равно обратная сортировка не нужна:

    arrayWords.reduce((prev, n) => {
      const p = document.createElement('p');
      p.innerHTML = n;
      prev.after(p);
      return p;
    }, document.querySelector('.listing'));
    Ответ написан
    Комментировать
  • Как сделать, чтобы выбранное значение в списке заменяло собой список?

    0xD34F
    @0xD34F Куратор тега JavaScript
    $('table').on('change', 'select', ({ target: t }) => {
      $(t).replaceWith(t.value);
      // или
      $(t).parent().text(t.value);
    });

    или

    document.querySelector('table').addEventListener('change', ({ target: t }) => {
      if (t.tagName === 'SELECT') {
        t.parentNode.replaceChild(document.createTextNode(t.value), t);
        // или
        t.parentNode.textContent = t.value;
      }
    });
    Ответ написан
    Комментировать
  • Как более компактно получить тексты ошибок и выводить цвета?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Цвета:

    progress.css('background-color', [
      { min: 100, color: '#47C965' },
      { min:  40, color: '#f5dd30' },
      { min:   0, color: '#bf4542' },
    ].find(n => n.min <= strength).color);

    Тексты ошибок: не надо никаких атрибутов, сразу положите их в объекты.
    Но если всё же хотите идти путём говнокодера, то
    const tests = [ здесь перечисляете регулярные выражения ].map((n, i) => ({
      regex: n,
      message: error_wrap.attr(`data-error_${i + 1}`),
    }));
    Ответ написан
    1 комментарий
  • Как найти дубликаты в массиве?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Собрать новый массив:

    const newArr = Object
      .values(arr.reduce((acc, n, i) => ((acc[n] ??= []).push(i), acc), {}))
      .reduce((acc, n) => (n.forEach(i => acc[i] = n.length > 1), acc), []);
    
    // или
    
    const newArr = arr.map(function(n) {
      return this[n];
    }, arr.reduce((acc, n) => (acc[n] = acc.hasOwnProperty(n), acc), {}));
    
    // или
    
    const count = arr.reduce((acc, n) => (acc[n] = (acc[n] ?? 0) + 1, acc), {});
    const newArr = arr.map(n => count[n] > 1);
    
    // или
    
    const newArr = arr.map((n, i, a) => a.indexOf(n) !== a.lastIndexOf(n));

    Обновить существующий:

    arr.forEach(function(n, i, a) {
      a[i] = this.get(n) > 1;
    }, arr.reduce((acc, n) => acc.set(n, -~acc.get(n)), new Map));
    
    // или
    
    const duplicates = arr.reduce((acc, n) => acc.set(n, acc.has(n)), new Map);
    arr.splice(0, arr.length, ...arr.map(n => duplicates.get(n)));
    Ответ написан
    2 комментария
  • Как получить текст выбранного option'а?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Поскольку в показанном select'е у option'ов отсутствуют атрибуты value, то их значениями будет их текстовое содержимое. Так что в данном конкретном случае текст можно получить с помощью метода val.

    Ну а вообще:

    $('select').change(function() {
      const text = $(':checked', this).text();
      console.log(text);
    });

    Или, к чёрту jquery:

    document.querySelector('select').addEventListener('change', function(e) {
      const select = this;
      // или
      // const select = e.target;
      // const select = e.currentTarget;
    
      const option = select.selectedOptions[0];
      // или
      // const option = select.options[select.selectedIndex];
      // const option = select.querySelector(':checked');
      // const option = [...select.children].find(n => n.selected);
    
      const text = option.text;
      // или
      // const text = option.textContent;
      // const text = option.innerText;
    
      console.log(text);
    });
    Ответ написан
    Комментировать
  • Как сделать фильтрацию результатов по поиску?

    0xD34F
    @0xD34F Куратор тега JavaScript
    document.querySelector('#filter-input').addEventListener('input', e => {
      const val = e.target.value.toLowerCase();
    
      container.querySelectorAll('.title').forEach(n => {
        n.closest('.card').style.display = n.innerText.includes(val)
          ? 'block'
          : 'none';
      });
    });
    Ответ написан
    1 комментарий
  • Как динамично сформировать объект?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const obj = Object.fromEntries(arr.map((n, i) => [ `answer${i + 1}`, n ]));

    или

    const obj = arr.reduce((acc, n, i) => (acc['answer' + ++i] = n, acc), {});

    или

    const obj = {};
    for (let i = 0; i < arr.length; i++) {
      obj['answer'.concat(-~i)] = arr[i];
    }
    Ответ написан
    5 комментариев
  • Как проверить регистр первой буквы?

    0xD34F
    @0xD34F Куратор тега JavaScript
    str[0] === str[0].toUpperCase()

    или

    /^[A-Z]/.test(str)

    или

    (c => 64 < c && c < 91)(str.charCodeAt(0))

    или

    'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.includes(str.at(0))

    Но что если там вообще не буква?
    Если такое может быть, то...
    ...ни хрена тут больше не скажу, тщательнее надо было вопрос продумывать.
    Ответ написан
    Комментировать
  • Как поменять класс, если выбран checkbox?

    0xD34F
    @0xD34F Куратор тега JavaScript
    $('.filter').change(({ target: t }) => {
      $(`[name="${$(t).closest('.button').data('size')}"]`)
        .closest('.product-box')
        .toggleClass('hidden', !t.checked);
    }).find(':checked').change();

    Или, к чёрту jquery:

    const filter = document.querySelector('.filter');
    
    filter.addEventListener('change', ({ target: t }) => {
      const size = t.closest('.button').dataset.size;
      document.querySelectorAll(`[name="${size}"]`).forEach(n => {
        n.closest('.product-box').classList.toggle('hidden', !t.checked);
      });
    });
    
    filter.querySelectorAll(':checked').forEach(n => {
      n.dispatchEvent(new Event('change', { bubbles: true }));
    });
    Ответ написан
    1 комментарий