Как сохранить/зачеркнуть JavaScript-событие и повторно использовать его позже?

На странице https://developers.google.com/web/fundamentals/app-install-banners/#trigger-m68

let deferredPrompt;

window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault();
  // Stash the event so it can be triggered later.
  deferredPrompt = e;
});

Этот код в порядке, но я хочу запустить закрытое событие позже, в другом месте. Чтобы выполнить это, мне нужно сохранить событие не только в переменной, но и в другом месте.

Вопрос: как можно сохранить событие с помощью своих методов?

Я попробовал Local Storage с сериализацией/десериализацией объекта:

> localStorage.setItem('stashed-event', JSON.stringify(e))
>
> JSON.parse(localStorage.getItem('stashed-event'))

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

Ответ 1

Прочитав ваш вопрос несколько раз, а ответы еще несколько,

Вопрос: как можно хранить любой объект javascript с помощью его методов?

Ответ: нет никакого способа.

Тем не мение,

Джош правильно объяснил, что вы можете извлекать и хранить все сериализуемые свойства, например данные, из своего события.

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

Очевидно, что даже сериализованные в ваших данных свойства, такие как timeStamp, isTrusted и т.д., Будут завышены при создании нового события.

То, что вы просто пропустите/нужно, это EventTarget, значение свойства event.target, ссылка, которая теряется навсегда, когда document.body выгружается навсегда или при сериализации объекта события.

Но если он еще жив или если вы знаете, какой должен быть event.target, например элемент DOM или любой объект, который вы можете ссылаться, откуда бы вы не воссоздали событие (где?), Просто отправьте свое событие этому объекту, если оно выслушаете этот event.type, ваше новое событие должно быть, по крайней мере, услышано.

Простой пример из MDN EventTarget или см. EventTarget.dispatchEvent

В качестве комментария к обширному ответу cegfault: eval и текстовым исходным кодом... может быть <script> текстовый исходный код </script>... если ваш скрипт создает сценарий (String). Если нет, вы, вероятно, лучше пойдете дальше назад, где ваш скрипт создает неэриализуемые вещи, которые появляются в вашем случае, и подумайте о том, чтобы воссоздать эти вещи, а не событие.

Ответ 2

Было много разговоров об этом, как только I/O 2018 упомянул о том, что обработка A2HS-события является разработчиком, начиная с этого момента. Это также зафиксировано в официальном документе и вдохновлено им, есть красивая статья, в которой подробно объясняется, как достичь именно этого сценария. В то время как я предлагаю пройти полную статью для правильного понимания обновленной динамики вокруг потока A2HS, не стесняйтесь переходить в раздел "Новый добавочный поток" для вашего требования.

Вкратце, выполните следующие действия:

  1. Создайте переменную за пределами обработчика события beforeinstallprompt.
  2. Сохраните ссылку на beforeinstallprompt события beforeinstallprompt в вышеперечисленном обработчике.
  3. Используйте это позже, чтобы вызвать запрос добавления к домашнему экрану по требованию.

В статье есть полные фрагменты кода, которые вы можете ссылаться/повторно использовать.

Редактировать: я снова прочитал ваш вопрос и понял один важный аспект, который вы, возможно, особо искали, а именно: "где-то в другом месте". Если это означает, что вы ссылаетесь на его использование на другой странице, то моим предложением было бы пойти на сохранение объекта события в:

  1. IndexedDB, который представляет собой набор "хранилищ объектов", в которые вы можете просто вставлять объекты. Недостаток - могут быть ограничения совместимости с браузером. Кроме того, это может привести к большому количеству вложенных обратных вызовов.
  2. Или вы можете использовать "в кеше процесса" (кучную память вашего приложения), которая также не требует сериализации. Недостаток. Однако это нельзя использовать для нескольких серверов.

Помимо этого, я не могу предусмотреть бесплатное решение на данный момент. Но постараюсь понять это и, возможно, обновить поток.

Ответ 3

Вы не можете сохранить событие таким образом. Вы хотите сохранить объект. Для такого объекта сохраняются только сериализуемые свойства. Функции не могут быть сериализованы в JavaScript. Функции не могут быть сериализованы на многих языках.

В основном это в основном потому, что при десериализации объекта его подпись может измениться. Если вы когда-либо программировали в java, это похоже на ошибку десериализации при чтении в сериализованном объекте и попытке восстановления объекта. Поскольку тело функции метода объекта может меняться между временем, когда объект записывается в какое-либо хранилище, а затем читается позже, методы не могут быть сериализованы. Это связано с тем, что при сериализации объекта он не сериализует определение интерфейса, где определены методы. Он просто хранит данные.

По той же причине, когда вы сериализуете строку json, она отбрасывает функции.

Вместо хранения события храните полезную информацию из события в объекте (или пусть вещи неявно удаляются, строгая и используя событие напрямую).

Какой метод хранения, который вы используете, зависит только от вещей, не упомянутых в вашем вопросе. Например, как долго он должен храниться, независимо от того, должен ли он быть доступен вне вашего сайта, сколько данных обычно будет храниться, есть ли более одного объекта для хранения и т.д. На основании ограниченной информации, предоставленной в вашем вопросе, вы, вероятно, просто используете либо localStorage, либо массив памяти.

Если вы обнаружите необходимость хранить сотни объектов, то indexedDB станет более подходящим. Но просто выбор другого носителя информации не будет иметь никакого влияния на то, можете ли вы хранить функции. Вы не можете хранить функции.

Ответ 4

TL; DR, чтобы выполнить то, что вы делаете, у вас есть три варианта:

  1. Храните ссылку на событие в глобальном значении (это то, что большинство учебных пособий, например, ваше видео с ссылкой на youtube), порекомендуют вам). Это требует, чтобы событие запускалось в том же контексте (например, веб-страница), как при сохранении ссылки
  2. Когда вы храните ссылку на событие в localStorage (например, по имени или по ключевому слову/значению), на странице/контексте, где вы хотите выполнить событие, убедитесь, что соответствующие функции и библиотеки загружены перед выполнением события
  3. [настоятельно НЕ рекомендуется] Храните исходный код javascript в вашем хранилище и eval() позже [снова, пожалуйста, не делайте этого]

Как упоминалось @Josh и @SaurabhRajpal, то, что вы просите, строго говоря, невозможно в JavaScript. То, что вы делаете с JSON.stringify(e), вероятно, вернет undefined или null, поскольку документация MDN для JSON.stringify говорит:

Если undefined, функция или символ встречается во время преобразования, то она либо опускается (когда она найдена в объекте), либо подвержена цензуре до нуля (когда она найдена в массиве). JSON.stringify также может просто вернуть undefined при передаче "чистых" значений, таких как JSON.stringify(function() {}) или JSON.stringify(undefined).

Короче говоря, нет способа сохранить одну функцию в localStorage (или в любом другом автономном хранилище). Чтобы объяснить, почему это невозможно, см. Этот пример:

function foo() {
    console.log("a")
}

function bar() {
    return foo()
}

Как сохранить bar() для последующего использования? Чтобы хранить бар, вам также нужно будет хранить foo(). Это становится намного более сложным, если вы рассматриваете ссылку на функцию, которая находится или использует большую библиотеку (например, jQuery, underscore, D3, графические библиотеки и т.д.). Имейте в виду, что ваш компьютер уже проанализировал исходный код в двоичном формате и, как таковой, не будет легко знать, как читать эту функцию для всех возможных операторов if, for и switch для обеспечения сохранения всех возможных коррелированных функций и библиотек.

Если вы действительно хотели это сделать, вам придется написать свой собственный парсер javascript, и вы действительно не хотите этого делать!

Итак, каковы ваши варианты? Во-первых, сделайте все на одной странице и сохраните ссылку на событие в глобальном значении (в этом видео используется ссылка на YouTube, на которую вы ссылаетесь в комментарии).

Второй вариант - использовать ссылку на событие (а не само событие) и убедиться, что исходный код этой ссылки используется позже. Пример (html):

// on page #1
<script src="path/to/my/js/library.js"></script>
...
<script>
    window.addEventListener('beforeinstallprompt', (e) => {
        e.preventDefault()
        localStorage.setItem('stashed-event', 'before-install')
    })
</script>

// later, on page #2:
<script src="path/to/my/js/library.js"></script>
...
<script>
    var evt = localStorage.setItem('stashed-event', 'before-install')
    if(evt == 'before-install') {
        dosomething() // which would be a function in path/to/my/js/library.js
    }
    // another option here would be to define window listeners for all possible events
    // in your library.js file, and then simply build and trigger the event here. for
    // more about this, see: this link:
    // https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
</script>

Наконец, вы можете сохранить исходный код javascript, а затем eval() позже. Пожалуйста, пожалуйста, не делайте этого. Это плохая практика и может привести к очень злым вещам. Но если вы настаиваете:

// on page #1
window.addEventListener('beforeinstallprompt', (e) => {
    e.preventDefault()
    SomeAjaxFunction("path/to/my/js/library.js", function(responseText) {
        localStorage.setItem('stashed-event', {
            name: 'before-install',
            call: 'beforeInstallFunction()',
            src:  responseText
        })
    })
})

// later, on page #2:
var evt = localStorage.setItem('stashed-event', 'before-install')
if(evt) {
    console.log("triggering event " + evt.name)
    eval(evt.src)
    eval(evt.call)
}

Как я уже сказал, это действительно плохая идея, но это вариант.

IMHO, я думаю, вы пытаетесь избежать включения библиотеки или исходного кода в более позднюю страницу/приложение/все, и javascript просто не работает таким образом. Лучше всего передавать ссылки в памяти и использовать только хранилища ключей/значений для имен. Все остальное - это тип кодирующей гимнастики, чтобы избежать простого включения исходного кода в те места, в которые он должен быть включен.

Ответ 5

Вы можете создать глобальную константу и обновить ее, когда произойдет какое-либо событие, а не сериализовать ее и де-сериализовать, что является дорогостоящим процессом. Так вот как вы можете это сделать: вы можете создать экземпляр окна и клонировать событие в объекте окна, чтобы оно не зависело. (Обратите внимание, что это не будет работать через вкладки)

window.addEventListener('click', (e) => {
  e.preventDefault();
  window.deferredPrompt = Object.assign(e);//Don't mutate
});

let someOtherMethod = ()=>{
  console.log(window.deferredPrompt)
}

window.setInterval(someOtherMethod, 5000);

Ответ 6

Посмотрите, поможет ли это.

Определение прослушивателя событий для события "beforeinstallprompt"

window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault();
  //do all the stufff
  console.log('Event triggered');
});

Если вы хотите отправить мероприятие вручную. Создайте новую переменную события.

var myevent = new Event('beforeinstallprompt');
window.dispatchEvent(myevent);

Вывод "Событие запускается" в консоли.