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

    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 комментарий
  • Как удалить родительский элемент через дочерний?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Что помешало разобраться с этим вопросом самостоятельно? Всего-то надо было догадаться погуглить пару вещей:

    1. Как получить родительский элемент
    2. Как удалить элемент

    Нет, серьёзно - ЧТО ВАМ ПОМЕШАЛО?

    UPD.

    Кого надо удалить, на кого надо нажимать:

    const itemSelector = '.header';
    const buttonSelector = `${itemSelector} .delete_block`;

    Удаляем:

    document.querySelectorAll(buttonSelector).forEach(function(n) {
      n.addEventListener('click', this);
    }, ({ currentTarget: t }) => {
      while (!(t = t.parentNode).matches(itemSelector)) ;
      t.replaceWith();
    });
    
    // или
    
    document.addEventListener('click', e => e
      .target
      .closest(buttonSelector)
      ?.closest(itemSelector)
      .remove()
    );
    Ответ написан
    2 комментария
  • Как правильно скрестить 3 массива в один?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Почему именно три? Давайте сделаем функцию, которая будет обрабатывать произвольное количество массивов; а в качестве признака, по которому надо объединять их элементы, будет выступать не обязательно id:

    function merge(key, ...arrs) {
      const getKey = key instanceof Function ? key : n => n[key];
      const result = new Map;
    
      arrs.forEach(arr => arr.forEach(n => {
        const k = getKey(n);
        result.set(k, Object.assign(result.get(k) ?? {}, n));
      }));
    
      return [...result.values()];
    }

    Использовать так: 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, key) =>
      test(key, val)
        ? replacer(val)
        : val instanceof Object
          ? Object.entries(val).reduce((acc, [ k, v ]) => (
              acc[k] = replaceValues(v, test, replacer, k),
              acc
            ), val.constructor())
          : val;

    Рекурсии нет:

    function replaceValues(val, test, replacer) {
      const stack = [];
      const clones = new Map;
      const getClone = (v, k) =>
        test(k, v)
          ? replacer(v)
          : v instanceof Object
            ? (clones.has(v) || stack.push([ v, clones.set(v, v.constructor()).get(v) ]),
               clones.get(v))
            : v;
    
      if (val instanceof Object) {
        for (getClone(val); stack.length;) {
          const [ source, target ] = stack.pop();
          for (const k in source) if (Object.hasOwn(source, k)) {
            target[k] = getClone(source[k], k);
          }
        }
      }
    
      return getClone(val);
    }

    Как применять в вашем случае:

    const newData = replaceValues(
      data,
      k => k?.endsWith('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 setNested(root, ...args) {
      const val = args.pop();
      const key = (args = args
        .flat(Infinity)
        .flatMap(n => typeof n === 'string' ? n.split('.') : n))
        .pop();
    
      args.reduce((p, c) => p[c] ??= {}, root)[key] = val;
    }

    setNested(data, '0.arr.0.str', 'hello, world!!');
    setNested(data, 0, 'arr', 1, 'num', 666);

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

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

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

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

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

    const replaceNested = (val, replacer) =>
      val instanceof Object
        ? Object.entries(val).reduce((acc, [ k, v ]) => (
            acc[k] = replaceNested(v, replacer),
            acc
          ), val.constructor())
        : replacer(val);

    const newData = replaceNested(
      data,
      x => typeof x === 'string' && /^\d{2}\.\d{2}\.\d{4}$/.test(x)
        ? new Date(x.split('.').reverse().join('-'))
        : x
    );
    Ответ написан
    Комментировать
  • Как посчитать проценты?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Откуда брать значения и куда записывать их процентный эквивалент:

    const valueKey = 'number';
    const percentKey = 'percent';

    Что такое процент - отношение конкретного значения и суммы всех значений, умноженное на сто:

    const sum = arr.reduce((acc, n) => acc + n[valueKey], 0);
    const getPercent = n => n[valueKey] / sum * 100;

    Собираем новый массив или обновляем существующий:

    const newArr = arr.map(n => ({ ...n, [percentKey]: getPercent(n) }));
    
    // или
    
    arr.forEach(n => n[percentKey] = getPercent(n));
    Ответ написан
    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, '')
    
    // или
    
    ''.concat(...str.split(/(?<=\S)\s+(?=\S)/))
    
    // или
    
    str.match(/(^\s+)|\S+|(\s+$)/g)?.join('') ?? ''
    Ответ написан
    Комментировать
  • Как в 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
    О каких элементах идёт речь:

    const outerClass = 'filter__item';
    const innerClass = 'filter__checkgroup-count';

    Скрываем:

    OUTER:
    for (const n of document.getElementsByClassName(outerClass)) {
      for (const m of n.getElementsByClassName(innerClass)) {
        if (Number(m.innerText)) {
          continue OUTER;
        }
      }
    
      n.hidden = true;
      // или
      n.style.display = 'none';
      // или
      n.style.setProperty('visibility', 'hidden');
      // или
      n.style.cssText += 'opacity: 0';
      // или
      n.setAttribute('style', 'transform: scale(0)');
    }

    или

    .hidden {
      display: none;
    }

    document.querySelectorAll(`.${outerClass}`).forEach(n => {
      n.classList.toggle('hidden', !Array.prototype.some.call(
        n.querySelectorAll(`.${innerClass}`),
        m => +m.textContent
      ));
    });
    Ответ написан
    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
    Не будем ограничиваться двумя массивами, решим задачу в более общем виде - пусть источников данных будет произвольное количество. Возможны разные варианты:

    1. Если уверены, что длины всех массивов будут равны:

      const zip = (...arrs) =>
        arrs[0]?.map((n, i) => arrs.map(m => m[i])) ?? [];
      
      const result = zip(arr1, arr2);


    2. Вместо отсутствующих значений подставляем какое-то дефолтное:

      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 result = zip([ arr1, arr2 ]);.

      Пример с разными длинами массивов - zip([ [ 1, 2, 3 ], [], [ 99 ] ], Infinity) - в результате получим

      [
        [ 1, Infinity,       99 ],
        [ 2, Infinity, Infinity ],
        [ 3, Infinity, Infinity ]
      ]


    3. В качестве источников данных могут выступать не обязательно массивы:

      function* zip(data, defaultValue = null) {
        const iterators = Array.from(data, n => n[Symbol.iterator]());
      
        for (let doneAll = false; doneAll = !doneAll;) {
          const values = [];
      
          for (const n of iterators) {
            const { value, done } = n.next();
            values.push(done ? defaultValue : value);
            doneAll &&= done;
          }
      
          if (!doneAll) {
            yield values;
          }
        }
      }

      В вашем случае применять так: const result = [...zip([ arr1, arr2 ])];.

      Пример посложнее, результатом выражения

      Array.from(zip((function*() {
        yield [ , true, false, NaN ];
        yield 'abcde';
        yield Array(3).keys();
      })()))

      будет следующий массив:

      [
        [ undefined, 'a',    0 ],
        [      true, 'b',    1 ],
        [     false, 'c',    2 ],
        [       NaN, 'd', null ],
        [      null, 'e', null ]
      ]


    Ответ написан
    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);
    }
    Ответ написан
    Комментировать
  • Как разбить массив на подмассивы по дате?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Object.values(arr.reduce((acc, { date, ...n }) => (
      (acc[date] ??= { date, name_type: [] }).name_type.push(n),
      acc
    ), {}))

    Можно пример с lodash.

    _.map(_.groupBy(arr, 'date'), (v, k) => ({
      date: k,
      name_type: _.map(v, n => _.omit(n, 'date')),
    }))
    Ответ написан
    Комментировать