Задать вопрос
Ответы пользователя по тегу JavaScript
  • В чем причина ошибки 'expected "*\\n"' to equal '"*"'?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Ожидается '*' в качестве результата, реально вы выдаёте '*\n'.

    Лишний перенос строки в самом конце - тут лучше сделать массив, каждый элемент которого будет представлять отдельную строку результата, и склеить его в одну строку, типа .join('\n'). Ну и ещё пробелов не хватает после звёздочек.

    Исправлено.
    const christmasTree = length =>
      Array.from({ length }, (n, i) => (
        n = ' '.repeat(length - i - 1),
        n + '*'.repeat(i * 2 + 1) + n
      )).join('\n');
    Ответ написан
  • Как извлечь случайный элемент из вложенных массивов?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Извлечение случайного элемента массива выносим в отдельную функцию:

    const random = arr => arr[Math.random() * arr.length | 0];

    Дальше можно получить случайный элемент случайного элемента:

    const item = random(random(Object.values(state.themes)));

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

    const item = random(Object.values(state.themes).flat());

    Какой из вариантов предпочесть? Зависит от того, как должны распределяться вероятности выпадения элементов - в случае, если выбирать элемент в два приёма, и размеры вложенных массивов различны, вероятности будут разными для разных массивов. Предположим, есть два вложенных массива, в одном два элемента, во втором сто. Получается, у элементов первого массива шанс быть выбранными один к четырём (один из двух массивов, один из двух элементов - 0.5 умножается на 0.5), а у элементов второго - один к двумстам (0.5 * 0.01).

    То есть, если должны быть равны вероятности выпадения элементов - объединяем вложенные массивы в один. Если равными должны быть вероятности принадлежности выбранного элемента к вложенным массивам, то выбираем сначала массив, а потом элемент внутри него.

    Но что если вероятности выпадения элементов должны быть равны, а размеры вложенных массивов велики, и не хотелось бы тратить на их объединение ни время, ни память? Возвращаемся к двум выборам, но выбирать вложенные массивы надо не с равными вероятностями, а учитывая их размеры:

    function weightedRandom(arr, key = () => 1) {
      const val = key instanceof Function ? key : n => n[key];
      const max = arr.reduce((acc, n) => acc + val(n), 0);
    
      return () => {
        let rand = Math.random() * max;
        return arr.find(n => (rand -= val(n)) < 0);
      };
    }

    const randomArr = weightedRandom(Object.values(state.themes), 'length');
    
    // ...
    
    const item = random(randomArr());
    Ответ написан
    Комментировать
  • Как получить каждый последний подряд идущий элемент с нужным классом?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const elements = Array.prototype.filter.call(
      document.querySelectorAll('.green'),
      (n, i, a) => n.nextElementSibling !== a[i + 1]
    );

    Если отдельно стоящие элементы не интересуют, то замените селектор на '.green + .green', или при фильтрации дополнительно проверяйте, что n.previousElementSibling === a[i - 1].
    Ответ написан
    1 комментарий
  • Как получить часть строки после последнего «/»?

    0xD34F
    @0xD34F Куратор тега JavaScript
    str.split('/').pop()
    // или
    str.match(/[^\/]+$/)[0]
    // или
    str.replace(/.*\//, '')
    // или
    str.slice(str.lastIndexOf('/') + 1)
    // или
    Array.from(str).reduce((acc, n) => n === '/' ? '' : acc + n, '')
    // или
    [...str].filter((n, i, a) => !a.includes('/', i)).join('')
    Ответ написан
    1 комментарий
  • Как правильно скрестить 3 массива в один?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const merge = (key, ...arrs) =>
      Object.values(arrs.flat().reduce((acc, n) => (
        Object.assign(acc[n[key]] ??= {}, n),
        acc
      ), {}));
    
    
    const result = merge('id', arr1, arr2, arr3);
    Ответ написан
    Комментировать
  • Как можно сделать функцию перебора объекта проще/чистой?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Проще - используйте возможности массивов более полно:

    const getTruthyKeys = obj =>
      Object
        .entries(obj)
        .filter(n => n[1])
        .map(n => n[0]);

    Чистой - заведите себе привычку объявлять все используемые переменные. Сейчас ваша функция гадит в глобальную (или какую-то другую внешнюю, если там есть переменные с такими же именами) область видимости (не в строгом режиме и при отсутствии таких переменных во внешних областях видимости конечно, в этом случае ваш код просто упадёт с ошибкой), вот здесь:

    for([key, value] of Object.entries(item)) {


    UPD. А вообще, если подумать, то такая функция чистой быть не может. Уж точно не в js.
    Потому что в js можно сделать так:

    const obj = new Proxy({
      a: 0,
      b: 1,
      c: 2,
    }, {
      get: () => Math.round(Math.random()),
    });

    Ну и соответственно, результаты будут случаться для такого объекта самые разные:

    console.log(Array.from({ length: 10 }, () => getTruthyKeys(obj)));

    Ответ написан
    4 комментария
  • Как переписать значения во вложенных объектах?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const replaceValues = (val, test, replacer) =>
      val instanceof Array
        ? val.map(n => replaceValues(n, test, replacer))
        : val instanceof Object
          ? Object.fromEntries(Object
              .entries(val)
              .map(([ k, v ]) => [
                k,
                test(k, v)
                  ? replacer(v)
                  : replaceValues(v, test, replacer)
              ])
            )
          : val;
    
    
    const newData = replaceValues(
      data,
      k => k.includes('Date'),
      v => v.replace(/(\d+)-(\d+)-/, '$2.$1.')
    );
    Ответ написан
    6 комментариев
  • Как в jQuery улучшить функцию для работы с отдельными блоками?

    0xD34F
    @0xD34F Куратор тега JavaScript
    $('.content_toggle').click(function() {
      const $button = $(this);
    
      $(`#box-${this.id.split('-').pop()}`).slideToggle(300, function() {
        const isHidden = $(this).is(':hidden');
        $button
          .text(isHidden ? 'Показать текст' : 'Скрыть текст')
          .toggleClass('open', !isHidden);
      });
    
      return false;
    });

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

    $(this).closest('здесь селектор общего предка').find('.content_block')

    Или, если все элементы расположены последовательно, кнопка-блок-кнопка-блок-..., то тогда блок можно будет получить следующим образом:

    $(this).next()

    В случае, когда кнопки и соответствующие им блоки расположены в одном и том же порядке, есть вариант этим воспользоваться:

    $('.content_block').eq($('.content_toggle').index(this))
    Ответ написан
    Комментировать
  • Как скрыть блок комментариев к определенному ответу с jQuery и toggle?

    0xD34F
    @0xD34F Куратор тега JavaScript
    $(document).on('click', 'a[id^="answer_"]', e => $(`#comment_${e.target.id}`).toggle());
    Ответ написан
    1 комментарий
  • Как изменить значение в объекте по его пути?

    0xD34F
    @0xD34F Куратор тега JavaScript
    function setNestedVal(root, path, val) {
      const keys = path.split('.');
      const key = keys.pop();
      keys.reduce((acc, n) => acc[n] ??= {}, root)[key] = val;
    }
    
    
    setNestedVal(data, '0.arr.0.str', 'hello, world!!');
    setNestedVal(data, '0.arr.1.num', 666);

    Интересуют готовые решения

    https://lodash.com/docs/4.17.15#set

    А вообще, учитывая что за задачу вы пытаетесь решить...

    Нужно изменить этот массив, преобразовав все строки с датами в нём, независимо от того, как глубоко они вложены, в объекты.

    ...вам ничего из того, что выше, не нужно. Делаем функцию, которая рекурсивно обойдёт переданный объект, проверяя, чем являются значения - если строкой с датой, то выполняет указанное преобразование:

    const replaceValues = (data, test, transform) =>
      test(data)
        ? transform(data)
        : data instanceof Array
          ? data.map(n => replaceValues(n, test, transform))
          : data instanceof Object
            ? Object.fromEntries(Object
                .entries(data)
                .map(([ k, v ]) => [ k, replaceValues(v, test, transform) ])
              )
            : data;
    
    
    const newData = replaceValues(
      data,
      x => typeof x === 'string' && /^\d{2}\.\d{2}\.\d{4}$/.test(x),
      str => new Date(str.split('.').reverse().join('-'))
    );
    Ответ написан
    Комментировать
  • Как посчитать проценты?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Посчитать сумму значений всех элементов массива:

    const sum = arr.reduce((acc, n) => acc + n.number, 0);

    Затем собрать новый массив, где элементам будет добавлено новое свойство - отношение значения и суммы, умноженное на сто:

    const newArr = arr.map(n => ({ ...n, percent: n.number / sum * 100 }));
    Ответ написан
    1 комментарий
  • Как склеить значения одного ключа?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Object
      .entries(arr.reduce((acc, n) => (
        n = n.match(/('.*?'):'(.*?)'/),
        (acc[n[1]] ??= []).push(n[2]),
        acc
      ), {}))
      .map(n => `${n[0]}:'${n[1].join('|')}'`)
    Ответ написан
    3 комментария
  • Regex: как убрать все пробелы только между словами?

    0xD34F
    @0xD34F Куратор тега JavaScript
    str.replace(/(?<=\S)\s+(?=\S)/g, '')
    
    // или
    
    str.replace(/\S.*\S/g, m => m.replace(/\s/g, ''))
    Ответ написан
    Комментировать
  • Как в alpine js удалить элемент?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Очевидно, внести соответствующие изменения в данные, связанные с отображением элемента.

    Например.
    <script defer src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.3.4/cdn.min.js"></script>
    
    <div x-data="{
      items: [
        { id:  69, text:  'hello, world!!' },
        { id: 187, text:  'fuck the world' },
        { id: 666, text: 'fuck everything' },
      ],
    }">
      <template x-for="(n, i) in items">
        <div>
          <span x-text="n.text"></span>
          <button @click="items.splice(i, 1)">x</button>
        </div>
      </template>
    </div>

    https://jsfiddle.net/96ksgtdo/
    Ответ написан
    Комментировать
  • Как написать итератор для вывода нечетных чисел используя [Symbol.iterator]?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Код, который работает некорректно...

    Всё работает так, как и должно. После Object.freeze назначить новое свойство уже не получится. Можно записать итератор в прототип массива (ну и после того, как он отработает, вернуть оригинальный итератор на место):

    const originalIterator = Array.prototype[Symbol.iterator];
    
    Array.prototype[Symbol.iterator] = function() {
      let i = -1;
    
      return {
        next: () => (i += 2) < arr.length
          ? { done: false, value: this[i] }
          : { done: true },
      };
    };
    
    setTimeout(() => Array.prototype[Symbol.iterator] = originalIterator, 0);
    Ответ написан
    4 комментария
  • Как из массива массивов сделать один массив?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const result = arr.flat();
    
    // или
    
    const result = Array.prototype.concat.apply([], arr);
    
    // или
    
    const result = arr.reduce((acc, n) => (acc.push(...n), acc), []);
    
    // или
    
    const result = [];
    
    for (const n of arr) {
      for (const m of n) {
        result[result.length] = m;
      }
    }
    Ответ написан
    1 комментарий
  • Как скрыть родителя, если у детей определённое значение?

    0xD34F
    @0xD34F Куратор тега JavaScript
    document.querySelectorAll('.filter__item').forEach(n => {
      n.style.display = Array
        .from(n.querySelectorAll('.filter__checkgroup-count'))
        .some(m => +m.innerText.trim())
          ? 'block'
          : 'none';
    });

    или

    .hidden {
      display: none;
    }

    for (const n of document.getElementsByClassName('filter__item')) {
      n.classList.toggle('hidden', Array.prototype.every.call(
        n.getElementsByClassName('filter__checkgroup-count'),
        m => Number(m.textContent.trim()) === 0
      ));
    }
    Ответ написан
    1 комментарий
  • Как оставить уникальные объекты из двух массивов?

    0xD34F
    @0xD34F Куратор тега JavaScript
    function isEqual(a, b) {
      const keysA = Object.keys(a);
      const keysB = Object.keys(b);
      return keysA.length === keysB.length && keysA.every(n => a[n] === b[n]);
    }
    
    
    const result = arr1.concat(arr2.filter(n => arr1.every(m => !isEqual(n, m))));

    или

    const unique = function(arr, keys = n => n) {
      const picked = new Map;
      return arr.filter((...args) => {
        const p = []
          .concat(keys(...args))
          .reduce((acc, k) => acc.set(k, acc.get(k) ?? new Map).get(k), picked);
    
        return !p.set(this, p.has(this)).get(this);
      });
    }.bind(Symbol());
    
    
    const result = unique([ ...arr1, ...arr2 ], n => [ n.teamId, n.userId ]);
    Ответ написан
    Комментировать
  • Как получить массив массивов с 2-мя объектами из 2-ух массивов с объектами?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Не будем ограничиваться двумя массивами, решим задачу в более общем виде:

    // если уверены, что длины всех массивов будут равны
    const zip = arrs => arrs[0]?.map((n, i) => arrs.map(m => m[i])) ?? [];
    
    // или, вместо отсутствующих значений подставляем какое-то дефолтное
    const zip = (arrs, defaultValue = null) => Array.from(
      { length: Math.max(...arrs.map(n => n.length)) },
      (n, i) => arrs.map(m => i < m.length ? m[i] : defaultValue)
    );
    
    // или
    const zip = (arrs, defaultValue = null) =>
      arrs.reduce((acc, n, i) => (
        n.forEach((m, j) => (acc[j] ??= Array(arrs.length).fill(defaultValue))[i] = m),
        acc
      ), []);

    Как этим пользоваться: const arr = zip([ arr1, arr2 ]);.
    Ответ написан
    1 комментарий
  • Как разбить массив на части заданного размера?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Можно рассматривать нарезание массива на куски как самостоятельную операцию:

    const chunked = (arr, chunkSize) =>
      arr.reduce((acc, n, i) => (
        (i % chunkSize) || acc.push([]),
        acc.at(-1).push(n),
        acc
      ), []);
    
    
    console.log(chunked([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], 3));

    А можно - как частный случай группировки, и не обязательно одних только массивов:

    const group = (data, key) =>
      Array.prototype.reduce.call(
        data,
        (acc, n, i, a) => ((acc[key(n, i, a)] ??= []).push(n), acc),
        {}
      );
    
    const chunked = (data, chunkSize) =>
      Object.values(group(data, (_, i) => i / chunkSize | 0));
    
    
    console.log(chunked('0123456789', 3));
    console.log(chunked(document.querySelectorAll('img'), 5));

    Если режем на куски не массив, то и кусками тоже могут быть не массивы, а значения исходного типа (если данный тип поддерживает операцию получения куска значения):

    const chunked = (data, chunkSize, slice = data.slice) =>
      Array.from(
        { length: Math.ceil(data.length / chunkSize) },
        function(_, i) {
          return this(i * chunkSize, (i + 1) * chunkSize);
        },
        (slice instanceof Function ? slice : Array.prototype.slice).bind(data)
      );
    
    
    console.log(chunked('abcdefghij', 4)); // так кусками будут тоже строки
    console.log(chunked('abcdefghij', 4, [].slice)); // а так - массивы
    console.log(chunked($('img'), 5));

    В качестве исходных данных могут выступать не только массивоподобные объекты, но и итерируемые, а саму выдачу кусков можно оформить в виде генератора:

    function* chunked(data, chunkSize) {
      let chunk = [];
    
      for (const n of data) {
        if (chunk.push(n) === chunkSize) {
          yield chunk;
          chunk = [];
        }
      }
    
      if (chunk.length) {
        yield chunk;
      }
    }
    
    
    console.log(Array.from(chunked([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], 3)));
    console.log([...chunked(document.querySelectorAll('img'), 5)]);
    for (const n of chunked(Array(10).keys(), 4)) {
      console.log(n);
    }
    Ответ написан
    Комментировать