Задать вопрос
Ответы пользователя по тегу JavaScript
  • Устарел ли getElementsBy* и чем лучше querrySelector?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Вот народ ушел в спор о производительности, но никто даже не попытался разобраться, а что под капотом... Производительность ведь искусственными бенчмарками меряют, ага...
    Что ж, времена сейчас такие
    многие на работу кодеров берут, которые даже интереса не имеют в глубь копать. Инженеров брать... - это устаревший подход, как выразился автор "популярного сайта", который прочел автор вопроса. Инженеры они дорогие, и найти их сложно, лучше кодер, пусть и не желающий на работе мозг включать, не говоря уж о желании в устройстве инструментов разбираться.

    Говорить о том, что некие фичи устарели - крайне глупо, если знать, что они ведут себя иначе, чем более модные альтернативы. Предлагаю немного разобраться и начать с того что на поверхности:
    - getElementById и querySelector возвращают конкретную ноду в единственном экземпляре
    - querySelectorAll и getElementsByName возвращает статичную коллекцию NodeList
    - getElementsByClassName, getElementsByTagName и getElementsByTagNameNS возвращают динамическую коллекцию HTMLCollection
    Как видим результат у разного апи различен, а значит и говорить, что некоторые из них устарели - глупо.
    Правда тут есть забавный момент
    в спеке HTMLCollection отмечен как "исторический артефакт", который не стоит использовать при проектировании нового апи. Но заметка эта не для веб-разработчиков, а для тех кто проектирует новое DOM апи.

    С устареванием вроде разобрались, но в вопросе еще есть часть "чем лучше". И тут есть теория, что getElementsBy* быстрее querySelector*. Чисто теоретически звучит логично, querySelector* должен делать полный поиск по дереву со сложностью O(n), а getElementsBy* могут использовать индексы на базе HashMap и получать данные со сложностью O(1), тут и особенности HTMLCollection будут кстати, так как можно не копировать коллекцию, а отдавать одну и ту же (и браузеры действительно так делают). Но без пруфов теория так и останется теорией.
    И бенчмарки - так себе пруф
    Хотя направлять инвесторов в нужную сторону - самое то. Проблема бенчмарков, что можно написать их так, что любая из сравниваемых сторон заметно обгонит другую. Дело техники. Например BubbleSort с O(n2) при определенных условиях в чистую уделывает MergeSort и QuickSort с их O(n×log2n), а именно при n=20 или меньше, 400 простых memswap в наглую рвут 87 рекурсивных операций с memcpy внутри
    Гораздо лучше тут выглядят исходники. И я выбрал исходники chromium, как самого распространенного:
    - getElementsBy* методы все как один обращаются к некой шаблонной функции EnsureCachedCollection, которая в свою очередь обращается к некоему NodeLists кэшу, устроенному внутри действительно как HashMap или что-то наподобие. Никакого поиска тут нет, просто берутся готовые значения, сложность у всего этого действительно константная O(1).
    - querySelector* используют абстракцию SelectorQuery, которая и в самом деле делает поиск по DOM. Но данный поиск неплохо оптимизирован и обвешан кэшами. Притом CSSOM использует абсолютно тот же алгоритм поиска DOM нод для каждого селектора в css.
    Для примера
    в подключенных на странице этого вопроса стилях более 1600 правил (некоторые из которых потенциально могут содержать несколько селекторов), полная обработка стилей из этого файла заняла на моей машине (Ryzen 3600 в стоке) чуть больше 9 мс. Если все это немного округлить, то понадобится 15000 querySelectorAll подряд, притом с разными селекторами, чтоб был промах кэша, дабы я ощутил заметную глазу задержку в ~100мс


    На этом спор думаю можно закрыть. querySelector* методы действительно могут быть медленнее, но чтоб убить ими производительность, нужно очень постараться. На фоне того, что пишут кривые ручки среднестатистического дешевого js-кодера это будет незначительной потерей измеряемой в наносекундах. Используйте то что удобнее в каждой конкретной ситуации.
    Ответ написан
    1 комментарий
  • Как отсортировать массив массивов строк в js?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Данная задача в принципе не решается сортировкой, так как сортировка это про отношение больше/меньше/равно, которого в данном случае нет.
    Самое простое здесь, это построить из этих элементов двусвязный список, а затем преобразовать его в результирующий массив:
    function orderArray(arr) {
      // для начала построим ноды списка и соберем их в 2 HashMap по обоим строкам
      const maps = arr.reduce((acc, item) => {
        const node = {item, next: null, prev: null};
        acc[0][item[0]] = node;
        acc[1][item[1]] = node;
        return acc;
      }, [{}, {}]);
    
      // после пройдемся по обоим HashMap и соединим связи
      for(const key of Object.keys(maps[0])) {
        maps[0][key].next = maps[0][maps[0][key].item[1]] || null;
      }
      for(const key of Object.keys(maps[1])) {
        maps[1][key].prev = maps[1][maps[1][key].item[0]] || null;
      }
    
      // найдем начальную ноду списка (ноду без предыдущей ноды)
      let cur = Object.values(maps[0]).find(({prev}) => prev === null);
    
      // и начиная с нее соберем список в массив
      const result = [];
      while(cur) {
        result.push(cur.item);
        cur = cur.next;
      }
    
      return result;
    }
    
    console.log(orderArray([['butter', 'jelly'], ['bananas', 'apples'], ['peanuts', 'butter'], ['jelly', 'bananas']]));
    Ответ написан
    1 комментарий
  • Могу ли я в чистом javascript в асинхронной функции подождать возникновения события?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Первое что нужно понимать, что async/await - это всего лишь синтаксический сахар над промисами, а значит имеет все особенности работы с ними:
    Во-первых, промис может разрешится (или отклонится, но в этой задаче reject не нужен) только единожды. Это сильно отличает их от событий, которые могут происходить многократно.
    Во-вторых, промисы обрабатываются на особой фазе event loop называемой microtasks, что опять же отличается от событий, которые выполняются на другой фазе (tasks). Это означает, что обработка промиса (колбэк метода then, или сахар над ним в виде await оператора) всегда произойдет асинхронно от возникновения события. Это накладывает ряд ограничений, например не получится сделать preventDefault у объекта или не получится запустить действия требующие синхронной работы из trusted события (play не muted video/audio, вход в фулскрин и тд).

    В простом варианте можно слушать событие разово, для чего можно использовать параметр once. Так же для оптимизации можно использовать параметр passive, так как нам все равно поздно вызывать preventDefault, а в некоторых случаях это может дать нам оптимизацию. Ну и управлять параметром capture попросту бесполезно, так как обработка будет в любом случае после обеих фаз. В итоге для этого можно пользоваться такой функцией хелпером:
    function listenOnce(target, event) {
        return new Promise(resolve => {
            target.addEventListener(event, resolve, {
                once: true,
                passive: true
            });
        });
    }
    
    // использование
    const event = await listenOnce(document, 'DOMContentLoaded');
    console.log(event);


    Если же нужно слушать событие многократно, то разумно обернуть прослушивание события в асинхронный итератор. Здесь так же присутствуют все ограничения связанные с промисами, но за счет того, что у нас будет итератор по множеству промисов, мы сможем слушать событие многократно. так же тут нужно предусмотреть возможность отписаться от события. Хелпер для данного случая получится такой:
    function listen(target, event) {
        let currentResolve = () => {};
        const handler = event => currentResolve({value: event, done: false});
        return {
            [Symbol.asyncIterator]() {
                target.addEventListener(event, handler, {passive: true});
                return {next: () => new Promise(resolve => {
                    currentResolve = resolve;
                })};
            },
            stop() {
                target.removeEventListener(event, handler);
                currentResolve({done: true});
            }
        };
    }
    
    // использование
    for await(const event = listenOnce(window, 'scroll')) {
        console.log(event);
    }
    
    // с отпиской
    const eventIterator = listenOnce(window, 'scroll');
    let count = 10;
    for await(const event = eventIterator) {
        console.log(event);
        if(--i === 0) {
            eventIterator.stop();
        }
    }
    Ответ написан
    4 комментария
  • Почему функции могут не работать?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    + к ответу WbICHA:
    yp += speed;
    какого поведения Вы ожидаете от присваивания в аргумент?
    Ответ написан
    8 комментариев
  • Можно ли код babel конвертировать обратно?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Ответ написан
    Комментировать
  • Как сделать наследование классов ES6?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    class Dropdown() {
      constructor(selector) {
      	this.$el = document.querySelector(selector);
      }
    
      toggle(cls) {
      	this.$el.classList.toggle(cls);
      }
    }
    
    class Nav extends Dropdown {
      constructor(selector) {
      	super(selector);
      	this.$btn = this.$el.querySelector('.nav-button');
        	this.$btn.addEventListener('click', this.toggle.bind(this, 'nav_open'));
      }
    }
    
    class Menu extends Dropdown {
      constructor(selector) {
      	super(selector);
      	this.$btn = this.$el.querySelector('.menu-button');
        	this.$btn.addEventListener('click', this.toggle.bind(this, 'menu_open'));
      }
    }
    
    new Nav('.nav');
    new Menu('.menu');
    так?
    Ответ написан
    2 комментария
  • Как правильно вывести такой код в innerHtml?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    // небольшая функция хелпер, чтоб писать меньше кода:
    const appendNewDiv = parent => parent.appendChild(document.createElement('div'));
    
    // создадим основные эллементы
    const wrap = appendNewDiv(albumTrack);
    const title = appendNewDiv(wrap);
    const itemAlbumWrap = appendNewDiv(wrap);
    const itemAlbum = appendNewDiv(itemAlbumWrap);
    
    wrap.classList.add('album__item');
    title.classList.add('album_title');
    itemAlbumWrap.classList.add('album__item__wrap');
    itemAlbum.classList.add('album__photo');
    
    // сюда будем складывать элементы, которые должны удалятся при смене number
    let elements = [];
    
    // функция, через которую меняем отображаемые данные по number
    const renderByNumber = number => {
        const {albumId, albums} = arr2[number];
        elements.forEach(e => wrap.removeChild(e));
        title.textContent = `Альбом ${albumId}`;
        elements = albums.map(album => {
            const e = appendNewDiv(wrap);
            e.textContent = album.title;
            return e;
        });
    };
    
    // при изменении number теперь просто вызываем
    renderByNumber(number);
    Ответ написан
    3 комментария
  • Как округлить число в Javascript до N знаков после запятой?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Комментировать
  • Отсортировать массив и вернуть bolean?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    const isSorted = arr => arr.every((el, i) => {
      if(i === 0) { return true; }
      const prev = arr[i - 1];
      const elType = typeof el;
      const prevType = typeof prev;
      if(elType === prevType) {
          return el >= prev;
      }
      return elType === 'string' && prevType === 'number'
    });
    isSorted([0, 1, 2, 2, 2, 3, 'a', 'b', 'w']); // true
    isSorted([0, 3, 1, 2, 2, 2, 'a', 'b', 'w']); // false
    isSorted(['a', 1, 2, 2, 2]); // false
    isSorted([1, 2, 2, 0, 'a']); // false
    Ответ написан
  • Почему не выполняется every?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    У HTMLCollection нет метода every, и метода includes тоже нет, как и метода some у DOMElement
    Ответ написан
    7 комментариев
  • Является ли строка выражением?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Является.
    Все что можно присвоить в переменную, вернуть из функции, передать в другую функцию - выражение.
    Ответ написан
    Комментировать
  • Как методы вроде foreEach передают аргумент в callback функцию?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Комментировать
  • Как сделать скролл на всю высоту экрана?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Если устроит поддержка в только новых браузерах, то есть нативная реализация в CSS:
    https://developer.mozilla.org/ru/docs/Web/CSS/CSS_...

    Для старых браузеров можно накидать опорных точек на страницу и эмитировать поведение через scrollIntoView:
    https://developer.mozilla.org/ru/docs/Web/API/Elem...
    Ответ написан
  • Как получить первые и последние слова всех строк текста?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Комментировать
  • Как при помощи JS создать CSS Selector для элемента имея на него ссылку?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    function calculateSelector(element) {
      let selector = '';
      for(let current = element; current?.nodeType === 1; current = current.parentElement) {
        if(current.id) {
          selector = `#${current.id}>${selector}`;
          break;
        }
        const tag = current.tagName.toLowerCase();
        const classes = Array.from(current.classList, cls => `.${cls}`).join('');
        selector = `${tag}${classes}>${selector}`;
      }
      return selector.slice(0, -1);
    }
    Ответ написан
    1 комментарий
  • Почему ветвление if-else срабатывает не правильно?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Потому что у Вас блок else срабатывает на каждой итерации цикла
    тык
    Ответ написан
    2 комментария
  • Почему выводит NaN?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Потому что Вы значения получаете тогда, когда поля еще пустые, а по нажатию кнопки просто их используете, нужно получать по нажатию кнопки, кроме того сам input не парсится в int, нужно парсить его свойство value:
    var input1 = document.getElementById("input1");
    var input2 = document.getElementById("input2");
    var button = document.getElementById("button");
    
    button.onclick = function(){
        var result = parseInt(input1.value) + parseInt(input2.value);
        document.body.append(result);
    }
    Ответ написан
    1 комментарий
  • Можно ли считать JavaScript полноценным языком программирования?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Может у него было тяжелое начало, но сейчас, можно ли его считать полноценным?
    JS тьюринг полный язык и всегда им был. Тьюринг полнота означает, что на нем можно посчитать все что в принципе вычислимо.

    Просто в нем даже импорт файла нормально нельзя сделать (даже в css он есть хоть и не полный)...
    Уже 5 лет как можно, в отличии, например, от C, где отдельные модули до сих пор нужно линковщиком собирать после компиляции. Так что, по Вашему C тоже не полноценный теперь?

    Нету многих приколов, фишек и функций, хотя я понимаю что внедрять их поздно, и для браузера он создавался.
    Хотелось бы конкретики, каких таких "приколов" Вам не хватает? Вот тут ребята открыты к предложениям: https://github.com/tc39/ecma262/blob/master/CONTRI...
    Ответ написан
    Комментировать
  • Реакция в 00:00, как сделать?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    function cloneOrCreateDate(date) {
      return date instanceof Date ? new Date(date) : new Date();
    }
    function getMidnightDate(date) {
      const d = cloneOrCreateDate(date);
      d.setHours(0);
      d.setMinutes(0);
      d.setSeconds(0);
      d.setMilliseconds(0);
      return d;
    }
    
    function addDaysToDate(days, date) {
      const d = cloneOrCreateDate(date);
      d.setDate(d.getDate() + days);
      return d;
    }
    
    function timeOffset(d1, d2) {
      return Math.abs(d1 - d2);
    }
    
    setTimeout(() => {
      // логика в полночь
    }, timeOffset(Date.now(), addDaysToDate(1, getMidnightDate())));
    Ответ написан
    3 комментария
  • Почему не работают функции на js?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Вы считали значения с полей 1 раз на старте программы, а нужно читать на каждое нажатие кнопки
    Ответ написан
    2 комментария