Не удалось создать ошибку "CustomElement", когда файл JavaScript помещен в голову

У меня есть пользовательский элемент, определенный так:

class SquareLetter extends HTMLElement {
    constructor() {
        super();
        this.className = getRandomColor();
    }
}
customElements.define("square-letter", SquareLetter);

Когда файл JavaScript включен в тег HTML <head>, консоль Chrome сообщает об этой ошибке:

Uncaught DOMException: Не удалось создать "CustomElement": результат не должен иметь атрибутов

Но когда файл JavaScript включен перед тегом </body>, все работает нормально. Какая причина?

<head>
    <script src="js/SquareLetter.js"></script> <!-- here -->
</head>
<body>
    <square-letter>A</square-letter>
    <script src="js/SquareLetter.js"></script> <!-- or here -->
</body>

Ответ 1

Ошибка правильная и может возникнуть в обоих случаях. Вам повезло, потому что некоторые текущие реализации пользовательских элементов не соблюдают это требование.

Конструктор для пользовательского элемента не должен читать или записывать его DOM. Он не должен создавать дочерние элементы или изменять атрибуты. Эту работу необходимо выполнить позже, обычно в методе connectedCallback() (хотя обратите внимание, что connectedCallback() можно вызывать несколько раз, если элемент удален и повторно добавлен в DOM, поэтому вам может потребоваться проверить это, или отменить изменения в disconnectedCallback()).

Цитируя спецификацию WHATWG HTML, выделение мое:

§ 4.13.2 Требования к конструкторам пользовательских элементов:

При разработке пользовательских конструкторов элементов авторы обязаны соблюдать следующие требования соответствия:

  • Вызов super() должен быть первым оператором в теле конструктора, чтобы установить правильную цепочку прототипов и это значение перед выполнением любого другого кода.

  • Оператор return не должен появляться где-либо внутри тела конструктора, если только это не простое раннее возвращение (возврат или возврат этого).

  • Конструктор не должен использовать методы document.write() или document.open().

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

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

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

  • В общем случае конструктор следует использовать для установки начального состояния и значений по умолчанию, а также для настройки прослушивателей событий и, возможно, теневого корня.

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

Когда вы перемещаете скрипт к элементу в DOM, вы заставляете существующие элементы проходить процесс "обновления". Когда сценарий находится перед элементом, элемент проходит стандартный процесс построения. Это различие, по-видимому, приводит к тому, что ошибка появляется не во всех случаях, а в том, что детали реализации могут измениться.

Ответ 2

Элемент еще не загружен, поэтому его нельзя изменить, загрузка script под элементом означает, что он может быть изменен