Ответы пользователя по тегу JavaScript
  • Как реализовать такое?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Для начала наш бэк должен уметь отвечать на запрос вида "дай мне N товаров после товара X", иначе такая кнопка в принципе не имеет смысл.
    Так вот, при нажатии на кнопку "Показать больше" мы первым делом должны ее заблокировать (чтоб пользователь не на тыкал лишнего) и показать лоадер (вдруг инет пользователя медленный), а затем отправить наш запрос к бэку, чтоб он дал нам данные о дополнительных товарах.
    Когда данные загрузились, отрисовываем их в конец и в зависимости от того, есть ли у бэка еще товары (это он тоже должен нам сказать) мы или разблокируем или прячем кнопку "Показать больше".
    Все.
    Ответ написан
    Комментировать
  • Как найти и заменить значение вложенного объекта?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    function cloneWithReplace(
        // клонируемый объект
        obj,
        // объект с функциями вида value => newValue, в соответствующих ключах для замены
        // нужен не всегда, поэтому по умолчанию сделаем пустой объект
        replacers = {},
        // Map с значениями, которые уже склонировали, дабы не попасть в рекурсию
        // так как внешний код обычно его не будет передавать, зададим значение по умолчанию
        storeMap = new Map()
    ) {
        // для начала чекнем, что объект уже клонировали:
        if(storeMap.has(obj)) {
            return storeMap.get(obj);
        }
    
        // получим тип объекта, он нам пригодится пару раз, дабы отличать функции
        const type = typeof obj;
    
        // если obj примитив, то его можно просто вернуть
        if(obj === null || (type !== 'object' && type !== 'function')) {
            return obj;
        }
    
        // создадим переменную с результатом и инициируем ее в зависимости от типа оригинала
        let result;
        if(type === 'function') {
            // функцию можно "склонировать" лишь обернув
            result = function() {
                return obj.apply(this, arguments);
            };
            // неплохо бы, чтоб клон правильно сообщал имя функции и количество аргументов
            // но так как IE не ест такую магию, обернем в try-catch
            try {
                Object.defineProperties(result, {
                    name: Object.getOwnPropertyDescriptor(obj, 'name'),
                    length: Object.getOwnPropertyDescriptor(obj, 'length')
                });
            } catch {}
        } else if(Array.isArray(obj)) {
            // массивы клонируем рекурсивно, при помощи map
            result = obj.map(value => cloneWithReplace(value, replacers, storeMap));
            // так как нормальные массивы не содержат других полей, кроме числовых
            // можно сохранить клон в защиту от рекурсии и вернуть результат
            storeMap.set(obj, result);
            return result;
        } else {
            // для всех других объектов просто создадим новый объект и скопируем ему ссылку на прототип
            result = Object.setPrototypeOf({}, Object.getPrototypeOf(obj));
        }
    
        // сохраним клон в защиту от рекурсии
        storeMap.set(obj, result);
    
        // осталось склонировать поля с заменой тех случаев, где у нас есть replacer
        for(const key of Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj))) {
            if(typeof replacers[key] === 'function') {
                // если есть replacer используем его
                result[key] = replacers[key](obj[key]);
            } else {
                // иначе клонируем поле рекурсивно
                result[key] = cloneWithReplace(obj[key], replacers, storeMap);
            }
        }
    
        return result;
    }
    
    // используем так:
    const newData = cloneWithReplace(data, {
        value: () => 'new value'
    });
    Ответ написан
    1 комментарий
  • Как изменить текст в буфере перед вставкой?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    document.addEventListener('paste', pasteText);
    function pasteText(e) {
      e.preventDefault();
      var txt = (e.originalEvent || e).clipboardData.getData('text/plain');
      e.target.value = txt;
    };
    Ответ написан
    1 комментарий
  • Выполнить функцию когда скролл на нужной позиции?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Комментировать
  • Как сравнить дату с датой из объекта?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Дату в объекте нужно распарсить в объект Date
    Сами даты приводятся операторами сравнения к числу (unix timestamp в милисекундах) и вполне себе сравниваются:
    const date = new Date('02-10-2005'); // это лучше вынести за цикл, что бы не делать много раз одно и то же
    for (card of json.CardInfo) {
       if(new Date(card.Date) < date) {
        console.log('true');
      } else{
        console.log('false');
      }
    }

    P.S. вообще конструкций вида if(...) { true } else { false } лучше избегать
    for (card of json.CardInfo) {
        console.log(String(new Date(card.Date) < date));
    }
    этот пример отработает абсолютно аналогично предыдущему, но читать кода тут меньше
    Ответ написан
    7 комментариев
  • Как работает сборщик мусор с колбеками Promise?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Сборщики мусора (далее GC) бывают разные, в том же v8 используется сразу 3 типа GC в зависимости от поколения объектов (упрощенно молодое, старое и сложные случаи), но в большинстве своем принцип работы сводится к просчету достижимости из некоторого корня в дереве объектов (например глобального объекта, но не только его). v8 не является исключением, и его GC содержит C++ api для создания таких корней. Из JS мы данным api можем воспользоваться лишь косвенно, через сущности языка предоставляемые либо самим v8 либо платформой (браузером, node.js, deno и т.д.)
    Чтоб было понятно давайте рассмотрим простой пример:
    const h = 'Hello world!';
    const n = 'nothing';
    setTimeout(() => {
      console.log(h);
    }, 1000);
    У нас есть строковые переменные h и n. Переменная n нигде больше не используется и ее память очистится при ближайшей работе GC. А вот переменная h оказалась в замыкании созданном стрелочной функцией. И хотя в JS мы не можем достучаться до h имея ссылку на эту функцию, сама функция все таки имеет ссылку на h, а значит h не может быть уничтожена пока не будет уничтожена сама функция. В терминах GC ссылка на h будет серой, то есть сама ссылка на h недоступна из корня напрямую, но сейчас мы проверяем объекты, которые на нее ссылаются и истина будет зависеть от них (подробнее можете погуглить "mark black white and gray memory in gc").
    Давайте посмотрим на саму стрелочную функцию, которая держит h в замыкании. Из кода видно, что мы ее передаем в функцию setTimeout, о которой известно, что это api предоставленное платформой (а значит вероятно какая-то часть написана на C++), а так же, что она асинхронная. Платформе реализующей setTimeout наша функция понадобится после асинхронного ожидания и никто платформе не сможет гарантировать, что во время этого ожидания не будет работы GC, поэтому ей ничего не остается, кроме как запросить у v8 создание нового корневого дерева объектов, в которое и будет положена ссылка на данную функцию.
    После же выполнения таймаута платформе больше не нужна наша функция, поэтому ссылка на нее будет удалена из дерева объектов. А так как других ссылок на функцию нет, и она больше не доступна ни из одного корня - GC удалит из памяти и функцию и строку связанную h, которая так же стала недоступна из корня.

    Посмотрим на пример из вопроса. У нас есть стрелочная функция, которая удерживает на себе инстанс компонента через this ссылку (так как стрелочные функции замыкают this). Саму функцию в памяти удерживает промис порожденный вызовом loader('url'), так как мы отдали её в метод then. Других ссылок на данную функцию нет, а значит и сама функция и ее замыкание (инстанс компонента) будут "жить" не менее "жизни" промиса.
    Скажем был отправлен запрос на сервер, но потом компонент в котором был объявлен промис и колбек был удален.
    И после удаления приходит ответ от сервера, и он выполнит колбек. Это значит что колбек остался в памяти со всеми переменными контекста
    Если других ссылок не осталось, то инстанс компонента будет удерживаться от очистки через промис.

    Теперь стоит разобраться с самим промисом. У него может быть 3 состояния - pending, resolved или rejected. После перехода в состояния resolved или rejected промис может выполнить сохраненные колбэки в ближайшем микротаске, а после он удалит на них ссылки из себя, в следствии чего, память удерживаемая замыканием колбэка может быть очищена (при отсутствии на нее других ссылок, достижимых из какого-либо корня).
    В состоянии pending промис может потенциально находится бесконечно долго, при этом ссылаясь на все колбэки переданные ему в методы then, catch или finally, а значит так же косвенно ссылаясь на их замыкания. И тут все зависит от того, кто ссылается на данный промис, и достижим ли он из корня. И да, промис вполне может быть удален из памяти так и не дождавшись своего завершения.
    интересное умозаключение
    Если Promise - это обещание, то в данном случае оно будет нарушено?


    В комментах к вопросу есть еще один интересный пример:
    function getSomething(){
      return new Promise((resolve, reject)=>{
        if(sys_condition){
           resolve();
        } 
      })
    }
    
    function testPromise(){
      let config = {....}
      getSomething().then(()=>{
         #use config
         goOn(...config)
      })
    }
    
    testPromise();
    У нас есть вызванная 1 раз функция testPromise, которая получает из функции getSomething промис, в который с помощью метода then сохраняет колбэк, удерживающий в замыкании переменную config. Сам промис она нигде не сохраняет, что здесь очень важно.
    В функции getSomething мы просто возвращаем промис созданный через его конструктор, который как мы уже знаем нигде больше не сохраняется. И на этом могло бы все и закончится, без вызова колбэка независимо ни от чего. Но конструктор промиса выполняет свой колбэк синхронно, а кроме того он передает в него 2 функции - resolve и reject, которые в своем замыкании ссылаются на только что созданный промис (а это уже 2 ссылки на него, хотя мы то его никуда не сохраняли). Переменная reject никак не используется, а значит спокойно может быть удалена после завершения колбэка. Переменная resolve просто вызывается как функция внутри условия, но более тоже никак не используется и никуда не сохраняется, а значит так же может быть удалена.
    В этом примере. если sys_condition = false и resolve не вызовется, это значит что создается утечка памяти
    Нет, утечки памяти не будет. Колбэк созданный в testPromise будет удален вместе с замыканием, так и не вызвавшись ни разу.
    Ответ написан
    3 комментария
  • Как отсортировать объект по вложенному массиву?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    const newArray = arr.slice(); // так как sort мутирует массив, а нужен новый
    const isAnnaSkill = skill => skill.name === 'Anna';
    const getAnnaLevel = item => item.skills.find(isAnnaSkill)?.level;
    newArray.sort((a, b) => getAnnaLevel(b) - getAnnaLevel(a));
    Ответ написан
    2 комментария
  • Как прокачать навыки функционального программирования?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Я бы начал с изучения книги Луиса Атенсио "Функциональное программирование на ...
    Дальше можно изучить серию статей "Мышление в стиле Ramda" на хабре
    Ответ написан
    Комментировать
  • Как посчитать количество повторений символов в строке?

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

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    То что у Вас в полях created и viewed вполне можно отдать в конструктор Date, а даты уже можно сравнивать между собой
    console.log(new Date("2020-05-22 16:24:16")); // работает

    Дальше, последний created, тут нужно уточнение:
    Если массив messages всегда упорядочен по полю created, как и в примере, то можно просто брать последний элемент массива и работать с ним:
    const lastMessage = messages[messages.length - 1];
    console.log(lastMessage);

    Если такой гарантии нет, то нужно искать максимум:
    const lastMessage = messages.reduce(([maxDate, maxMessage], currentMassage) => {
      const currentDate = new Date(currentMassage.created);
      if(currentDate > maxDate) {
        return [currentDate, currentMassage];
      }
      return [maxDate, maxMessage];
    }, [-Infinity, null])[1];
    console.log(lastMessage);

    Ну а если с этим разберетесь, то с последним пунктом у Вас уже не должно быть проблем, тут все аналогично
    Ответ написан
    3 комментария
  • Map это объект или массив?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    В js все объект, что не является примитивом (простые объекты, экземпляры классов, массивы, Map, Set, функции).
    Так что да, Map это объект.
    А вот массивом он не является, ибоArray.isArray(new Map()) // false
    Здесь есть вот такая штука [[Entries]]. А entries возвращает массив свойств объекта
    Вообще-то entries возвращает итератор. Итератор - это тоже не массив.
    Ответ написан
    2 комментария
  • Полноэкранный режим на сайте?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Проблема в том, что при переходе по ссылке документ, на котором вызван фулскрин, перестает существовать, а вместо него формируется новый, и при этом происходит выход из фулскрина.
    Решения тут я вижу два:
    1. Костыльное. Кроме документа фулскринить еще можно video и iframe, и iframe может тут помочь, так как он останется неизменным, сколько бы страниц в него не загружалось.
    2. Сложное. Можно переделать сайт в SPA, тогда настоящих переходов не будет, и все страницы будут работать в пространстве одного документа
    Ответ написан
  • Есть ли эффект-анимация перелистывания страниц книги со сменой url?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    А заглянуть как у них сделано?
    Вся магия тут: megaweb.su/demo/css3_book/css/style.css
    Ответ написан
  • Не копируется массив, в чем причина?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Не копируется, потому-что внутри массива maze у Вас лежат ссылки на подмассивы, и Вы копируете лишь их, а не сами массивы.
    Придется скопировать на уровень глубже:const mazeCopy = maze.map(sub => sub.slice())
    Ответ написан
    Комментировать
  • Как правильно разбивать зависимости npm пакета?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Хорошей практикой является не тащить зависимости в бандл.
    В webpack есть externals
    В rollup есть external
    Если Вы вшиваете свои зависимости в бандл своего пакета - Вы без вариантов создаете дублирование кода в бандле, если мне нужны те же зависимости, что и Вашему пакету и дополнительно Ваш пакет
    Ответ написан
  • Изменение action в форме. Корректно или нет?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    JS отработает раньше запросов, можете не париться
    Лучше задумайтесь, а что будет, если у меня не работает Ваш JS?
    Ну еще советую почитать про fetch апи, а так же, что по мимо click есть другие события
    Ответ написан
  • Ошибка Uncaught TypeError: ball.move is not a function?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Prop.__proto__.move поменять на Prop.prototype.move
    Prop.__proto__.check_collision поменять на Prop.prototype.check_collision

    Получше изучите прототипы, и чем prototype отличается от __proto__
    Ну и обратите внимание на es6 class, позволяющий работать с прототипами максимально просто и правильно
    Ответ написан
    Комментировать
  • VS Code в autocomplete не отображает методы и функции из lib.dom для this...?

    bingo347
    @bingo347 Куратор тега JavaScript
    Crazy on performance...
    Главное что нужно понять, что из коробки в VSCode подсказки в JS основаны на TypeScript, их можно расширить плагинами, но основа все равно останется именно такой.
    Так как в JS статических типов нет, VSCode пытается вывести тип с помощью TypeScript, но TypeScript делает это далеко не идеально.

    В случаеconst cnt = document.querySelector('.container')тип можно вывести однозначно - это тип Element, а все потому, что TS знает, что значение в const точно не изменится и просто берет возвращаемый тип для document.querySelector и выводит подсказки для него.

    В случае же с this.$element значение может поменяться, в том числе где-то из вне, и JS вполне позволяет изменить его на что угодно, поэтому выводится тип any, для которого подсказок нет.

    Я не буду Вас агитировать перейти на TypeScript, как и расписывать, насколько он экономит время при разработке чего-либо сложнее 10-20 строчек кода. Я просто подскажу, что TS в VSCode можно врубить и для обычных JS файлов, даже не имея tsconfig.json, достаточно просто добавить комментарий // @ts-checkв начало файла, и Вы получите сразу и больше подсказок, и некоторый контроль типов. Так же добавлю, что в js файлах можно аннотировать типы через jsdoc:
    // @ts-check
    export class Page {
        constructor(selector, options) {
            /** @type Element */    
            this.$element = document.querySelector(selector)
            this.$element // тут работают подсказки после точки для типа Element
        }
    }
    Ответ написан
    1 комментарий
  • Как можно другим способом найти сумму объекта?

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