11 votes

Est-ce que `document.write` est adapté pour trouver l'élément associé au script actuellement en cours d'exécution?

Comment puis-je trouver l'élément script associé au script actuellement en cours d'exécution ? Je m'intéresse uniquement aux scripts à l'intérieur du corps du document, pas dans l'en-tête (ou ailleurs).

Voici ce que j'ai jusqu'à présent, cela semble fonctionner correctement. Y a-t-il des pièges possibles auxquels je ne pense pas ?

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

Le cas d'utilisation que j'ai en tête pour cela implique des scripts qui ont besoin d'injecter du contenu au même endroit dans le document où la balise script apparaît, donc le chargement différé ne posera pas de problème. En d'autres termes, je n'appellerai cette fonction que dans des endroits où c'est approprié, elle ne sera pas exposée en tant que partie d'une API ou quoi que ce soit d'autre.

Je suis également conscient du problème avec document.write et XHTML, et je ne m'en fais pas dans ce cas (jusqu'à présent).


Ma tentative précédente ressemblait à ceci :

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

Mais, comme nous le voyons dans cette question, cela pourrait facilement échouer si un autre script a été injecté dans le document.


J'ai également trouvé document.currentScript dans ce spec du WHATWG. Il ne semble pas largement pris en charge pour le moment. Il y a un shim intéressant pour document.currentScript qui prend un autre chemin... Le script déclenche une erreur, l'attrape, inspecte la pile d'appels pour trouver l'URL du script, puis trouve l'élément script avec l'attribut src correspondant.

C'est une astucieuse solution, mais apparemment elle ne fonctionnera pas dans IE, et elle se casserait si plusieurs scripts utilisent des URL identiques.


Idéalement, j'aimerais trouver une solution qui donne le même résultat que l'exemple de code en haut (pas de nouvelles exigences), mais qui ne repose pas sur document.write. Est-ce possible ? Sinon, ce code est-il assez sûr en supposant qu'il est utilisé correctement (uniquement pendant le chargement de la page, pas en XHTML) ?


Édition : Voir cette réponse pour un autre cas d'utilisation potentiel, et des détails sur la raison pour laquelle un élément br est utilisé pour le placeholder.

2voto

Robert Points 101

OP a une bonne solution à un problème délicat. Cependant, voici quelques suggestions mineures pour l'améliorer.

La fonction devrait utiliser document.currentScript lorsqu'il est disponible. Cela fonctionne bien quelles que soient les circonstances. Disponible dans FireFox +4, Chrome +29 et Opera +16.

Avec IE 6-10, le script.readyState peut être utilisé pour émuler document.currentScript. Cela semble bien fonctionner quelles que soient les circonstances.

Pour éviter les problèmes, la fonction devrait se terminer lorsque document.readyState == "complete" ou même à l'étape "interactive" précédente en fonction du code. document.currentScript semble cesser de fonctionner lorsque "complete" est atteint. Cependant, document.write s'arrête à "interactive", c'est-à-dire que la méthode d'écriture de balise pour trouver les scripts échouera avec un script chargé de manière paresseuse.

L'attribut de script async devrait être vérifié avant d'utiliser document.write(). Les scripts marqués async sont détachés du document et la méthode document.write() ne fonctionnera pas comme prévu.

Le code OP écrit une balise

, mais une balise de style semble mieux fonctionner avec les scripts dans le corps et dans l'en-tête ... et sans effets secondaires.

L'ID de balise de numéro aléatoire ne semble pas servir à grand-chose puisque l'élément est supprimé. Il serait peut-être préférable d'utiliser une constante GUID réutilisée.

MISE À JOUR :

J'ai actualisé mon exemple de code ci-dessous pour illustrer une manière de le faire sans écrire de balises. Cela semble fonctionner correctement avec Chrome, FireFox, Opera et IE 5-10. Renvoie la valeur correcte quelle que soit les scripts injectés et les attributs du script (defer et async). La partie difficile est ce qu'il faut faire avec IE +11 ? Microsoft a supprimé script.readyState dans IE11 sans fournir d'alternative. Une solution de contournement consiste à utiliser "MutationObserver" pour anticiper les scripts. Dans IE, ces événements se produisent à chaque fois avant l'exécution d'un script (contrairement à onload qui se produit ensuite). Et cela fonctionne bien, sauf quand il y a des scripts chargés de manière externe avec des attributs defer ou async définis. Les scripts asynchrones sont particulièrement difficiles car ils peuvent s'exécuter à tout moment et pas toujours dans le même ordre (selon mon observation).

Quoi qu'il en soit, j'ai pensé poster ce code dans l'espoir qu'il puisse fournir un point de départ et aider d'autres personnes.

Références:

https://developer.mozilla.org/fr/docs/Web/API/MutationObserver

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

EXTRAIT DE CODE :

    var currentScript = (function() {

        // PRIVÉ
        var observateur, courant, nœuds;

        // IE+11 - non testé avec d'autres navigateurs.
        if (!document.currentScript && typeof MutationObserver=='function') {
            observateur = new MutationObserver(function(mutations) {
                if (document.readyState=='complete') observateur.disconnect();
                mutations.forEach(function(mutation) {
                    nœuds = mutation.addedNodes;
                    if (nœuds.length && nœuds[0].nodeName=='SCRIPT') {
                        courant = nœuds[0];
                    }
                });
            });
            observateur.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

0voto

PatAtCP Points 557

Si votre script n'est pas en ligne (par exemple, s'il est chargé via ''), générer une erreur dans un bloc try puis vérifier la trace de la pile dans le bloc catch. Vous pourrez alors déterminer le nom du fichier. Vous pouvez ensuite utiliser 'querySelector('[src="'+filename+'"]')' pour trouver la balise et insérer votre contenu avant celle-ci ou son élément frère.</p></x-turndown>

-1voto

Niet the Dark Absol Points 154811

En supposant que votre script n'est pas différé, vous pouvez simplement faire :

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

-2voto

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X