Перестает работать функция onсlick в innerHTML, если в html скрипту добавить type='module'

Имеется html документ, фрагмент из него:

<body class="body">
  <button class="hello">hello</button>  
</body>
<script src="index.js"></script>  

и к нему index.js скрипт:

const body = document.querySelector('.body')
const helloButton = document.querySelector('.hello')

helloButton.addEventListener('click', (e)=> hello(100500))
const x = 5;

function hello(number) {
  console.log('button',number)
}

for(let i=0;i<x;i++) {
  const button = `<button onclick='hello(${i})'}>${i}</button>`
  body.innerHTML += button;
}

Вопрос 1: в таком виде на страницу в цикле добавляется 5 кнопок, по нажатию на каждую срабатывает функция hello, в консоль выводится номер кнопки. Все ломается если при подключении скрипта в html, указать что это модуль, не могу понять почему:

<script type="module" src="index.js"></script>

Кнопки добавляются на страницу, но при нажатии получаю ошибки:

index.html:52 Uncaught ReferenceError: hello is not defined
    at HTMLButtonElement.onclick (index.html:52:9)

Вопрос 2: при наличии этого цикла с кнопками, нажатие на кнопку, которая уже есть в html, которой добавлен eventListener через js

<button class="hello">hello</button>

helloButton.addEventListener('click', (e)=> hello(100500))

Нажатие на кнопку ничего не дает, eventListener не отрабатывает, не важно стоит ли type='module' или нет. Если цикл убрать, кнопка работает в обоих вариантах. Прошу объясните, гуглеж и stackOverflow на конкретно этот случай ответа не нашел, даже на английском.


Ответы (1 шт):

Автор решения: SwaD

Модули - это "независимые" компоненты. Все объявленные переменные и функции доступны только внутри модуля. Для обмена переменными и функциями используются ключевые слова export/import.

Рассмотрим ваш пример с модулем и кнопкой.

Кнопка у вас создается так:

`<button onclick='hello(${i})'}>${i}</button>`

Где на событие клика должна присутствовать функция hello в текущем локальном или глобальном окружении, однако функция объявлена в модуле и не доступна из текущего окружения кнопки.

Я вижу 2 рещения:

  1. Создавать кнопку с помощью document.createElement и вешать ей событие клика через addEventListener в том же модуле
  2. После объявления функции hello выполнить window.hello = hello;. Это положит функцию hello в глобальное пространство браузера (не рекомендую)

Вот ваш HTML контейнер

<body class="body">
  <button class="hello">hello</button>  
</body>

Вы создали собыие клика:

const helloButton = document.querySelector('.hello')
helloButton.addEventListener('click', (e)=> hello(100500))

Далее вы в цикле каждую итерацию удаляете все из контейнера и заново добавляете.

Вот этот код

for(let i=0;i<x;i++) {
  const button = `<button onclick='hello(${i})'}>${i}</button>`
  body.innerHTML += button;
}

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

for(let i=0;i<x;i++) {
  // Создали строку
  const button = `<button onclick='hello(${i})'}>${i}</button>`;
  // Получили содержимое контейнера в виде строки
  const prevHTML = body.innerHTML;
  // Говорим, добавь строку, но преобразуй ее в HTML элементы
  body.innerHTML = prevHTML + button;
}

т.е. каждая итерация улаляет все содержимое и устанавливает заново НОВОЕ. Все события, которые были у удаляемых элементов удаляются.

→ Ссылка