Является ли `document.write` подходящим для поиска элемента, связанного с текущим запуском script?

Как я могу найти элемент script, связанный с текущим запуском script? Меня интересуют только скрипты внутри тела документа, а не в голове (или в другом месте).

Вот что я до сих пор, похоже, все в порядке. Есть ли возможные подводные камни, о которых я не думаю?

function getCurrentScriptElement() {
    var placeholderId = 'placeholder-' + Math.random(), 
        script, placeholder;
    document.write('<br id="' + placeholderId + '">');
    placeholder = document.getElementById(placeholderId);
    script = placeholder.previousSibling;
    placeholder.parentNode.removeChild(placeholder);
    return script;
}

В этом случае я имею в виду сценарии, которые должны вводить контент в том же месте в документе, где встречается тег script, поэтому отложенная загрузка не будет проблемой. Другими словами, я буду называть эту функцию только в тех местах, где она подходит, она не будет отображаться как часть API или что-то еще.

Я также знаю проблему с document.write и XHTML, и я не беспокоюсь об этом в этом случае (пока).


Моя предыдущая попытка выглядела примерно так:

function getCurrentScriptElement() {
    var scripts = document.getElementsByTagName('script');
    return scripts[scripts.length - 1];
}

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


Я также нашел document.currentScript в этом WHATWG spec. На данный момент это, похоже, не поддерживается широко. Там интересная прокладка для document.currentScript, которая принимает другой маршрут... script вызывает ошибку, ловит ее, проверяет трассировку стека чтобы найти URL-адрес script, а затем найдет элемент script с соответствующим атрибутом src.

Это умное решение, но оно, по-видимому, не будет работать в IE, и оно сломается, если несколько скриптов используют одинаковые URL-адреса.


В идеале я хотел бы найти решение, которое дает тот же результат, что и образец верхнего кода (без дополнительных требований), но не полагается на document.write. Это возможно? Если нет, является ли этот код достаточно безопасным, если он используется правильно (только во время загрузки страницы, а не XHTML)?


Изменить: см. этот ответ для другого потенциального варианта использования и подробную информацию о том, почему элемент br используется для заполнителя.

Ответ 1

OP имеет хорошее решение сложной проблемы. Тем не менее, здесь есть несколько незначительных предложений по его улучшению.

Функция должна использовать document.currentScript, когда она доступна. Он хорошо работает независимо от ситуации. Доступно в FireFox +4, Chrome +29 и Opera +16.

В IE 6-10 можно использовать script.readyState для эмуляции document.currentScript. Он также работает хорошо, независимо от ситуации.

Чтобы избежать проблем, функция должна выйти, когда document.readyState == "завершена" или даже на более раннем "интерактивном" этапе в зависимости от кода. document.currentScript перестает работать, когда "завершен". Однако document.write останавливается на "интерактивном", т.е. Метод записи меток для поиска скриптов будет терпеть неудачу с ленивым загруженным script.

Атрибут script async должен быть проверен перед использованием document.write(). Сценарии с пометкой async отделяются от документа, а метод document.write() не работает должным образом.

OP-код записывает <p> , но тег стиля, похоже, лучше работает со сценариями в теле и голове... и без побочных эффектов.

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

UPDATE:

Я обновил свой пример кода ниже, чтобы проиллюстрировать способ сделать это без написания тегов. Кажется, он отлично работает с Chrome, FireFox, Opera и IE 5-10. Возвращает правильное значение независимо от введенных скриптов и атрибутов script (отложить и асинхронно). Трудная часть - что делать с IE +11? Microsoft удалила script.readyState в IE11 без предоставления альтернативы. Одним из способов является использование "MutationObserver" для поиска сценариев. В IE эти события происходят каждый раз перед выполнением script (в отличие от onload, который происходит после этого). И это хорошо работает, за исключением случаев, когда есть загруженные извне скрипты с установленными атрибутами отсрочки или асинхронизации. Асинхронные скрипты особенно сложны, потому что они могут выполняться в любое время и не всегда в одном порядке (мое наблюдение).

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

Литература:

https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

http://msdn.microsoft.com/en-us/library/ie/dn265034 (v = vs .85).aspx

CODE SNIPPET:

    var currentScript = (function() {

        // PRIVATE
        var observer, current, nodes;

        // IE+11 - untested with other browsers.
        if (!document.currentScript && typeof MutationObserver=='function') {
            observer = new MutationObserver(function(mutations) {
                if (document.readyState=='complete') observer.disconnect();
                mutations.forEach(function(mutation) {
                    nodes = mutation.addedNodes;
                    if (nodes.length && nodes[0].nodeName=='SCRIPT') {
                        current = nodes[0];
                    }
                });
            });
            observer.observe(window.document, {childList: true, subtree: true});
        }

        // PUBLIC
        return function() {

            if (document.readyState=='complete') return;

            // CHROME+29, FIREFOX+4, OPERA+16
            if (document.currentScript) return document.currentScript;

            var i, s=document.getElementsByTagName('SCRIPT');

            // MSIE+5-10
            if (s[0].readyState) 
                for(i=0; i<s.length; i++)
                    if (s[i].readyState=='interactive') return s[i];

            // IE+11
            if (current) return current;

            // BEST GUESS
            return s[s.length-1];

        }

    })()

Ответ 2

Если ваш script не является встроенным (например, он загружен через < script src > , то отметьте ошибку в блоке try, а затем проверьте трассировку стека в блоке catch. имя файла. Затем вы можете использовать querySelector ('[src= "' + filename + '" ]'), чтобы найти тег и вставить свой контент до него или его родного брата.

Ответ 3

Предполагая, что ваш script не отложен, вы можете просто сделать:

var allScripts = document.getElementsByTagName("script"),
    currentScript = allScripts[allScripts.length-1];

Ответ 4

Еще одна идея: согласно w3schools тег <script> поддерживает глобальные атрибуты HTML.

Использование любого из глобальных атрибутов (id, class, title). Вы можете различать теги script, хотя в этом случае вам необходимо предварительно определить id и class.