Gli script possono essere inseriti con innerHTML?


223

Ho provato a caricare alcuni script in una pagina usando innerHTMLsu a <div>. Sembra che lo script si carichi nel DOM, ma non viene mai eseguito (almeno in Firefox e Chrome). C'è un modo per far eseguire gli script quando li si inserisce innerHTML?

Codice di esempio:

<!DOCTYPE html>
<html>
  <body onload="document.getElementById('loader').innerHTML = '<script>alert(\'hi\')<\/script>'">
    Shouldn't an alert saying 'hi' appear?
    <div id="loader"></div>
  </body>
</html>

Risposte:


88

Devi usare eval () per eseguire qualsiasi codice di script che hai inserito come testo DOM.

MooTools lo farà automaticamente per te, e sono sicuro che lo farebbe anche jQuery (a seconda della versione utilizzata da jQuery versione 1.6+ eval). Questo evita molta seccatura di analizzare i <script>tag e sfuggire ai tuoi contenuti, così come un mucchio di altri "trucchi".

Generalmente, se ci vai da eval()solo, vuoi creare / inviare il codice dello script senza alcun markup HTML come <script>, poiché questi non funzioneranno eval()correttamente.


12
Quello che voglio davvero fare è caricare uno script esterno, non solo valutare alcuni script locali. L'aggiunta di un tag di script con innerHTML è molto più breve della creazione di un elemento DOM di script e dell'aggiunta al corpo, e sto cercando di rendere il mio codice il più breve possibile. Devi creare gli elementi dello script dom e aggiungerli al dom piuttosto che usare qualcosa come innerHTML? C'è un modo per farlo con document.write dall'interno di una funzione?
Craig,

5
Come suggerisce zombat, usa un framework Javascript per caricare lo script esterno, non provare a reinventare la ruota. JQuery lo rende estremamente semplice, basta includere JQuery e chiamare: $ .getScript (url). È inoltre possibile fornire una funzione di callback che verrà eseguita una volta caricato lo script.
Ariel Popovsky,

2
Ariel ha ragione. Apprezzo il tentativo di mantenere il codice breve e l'aggiunta di un <script>tag con innerHTMLpotrebbe essere breve, ma non funziona. È tutto solo testo fino a quando non viene eseguito eval(). E purtroppo, eval()non analizza i tag HTML, quindi si finisce con una catena di problemi.
zombat,

21
eval () non è un'ottima soluzione per qualsiasi problema.
Buley,

2
Ho provato eval () me stesso. È un'idea orribile. Devi valutare tutto OGNI TEMPO . Anche se dichiari un nome e un valore di variabile, devi ri-dichiararlo / ri-eval () ogni volta di nuovo per farlo funzionare. È un incubo di errori.
Youstay Igo,

88

Ecco una soluzione molto interessante al tuo problema: http://24ways.org/2005/have-your-dom-and-script-it-too

Quindi usa questo invece di tag di script:

<img src="empty.gif" onload="alert('test');this.parentNode.removeChild(this);" />


12
È brillante!
Confile

Non funziona per me, se inserito in un risultato di una richiesta Ajax: errore di sintassi mancante; prima dell'inizio della stringa di script
Oliver

Come aggiungete la linea & lt; img src ... al codice della vostra pagina? Document.write () o usi document.body.innerHTML + = approccio per questo? Entrambi stanno fallendo per me :(
Youstay Igo,

Non molto pratico per scrivere molto codice all'interno di un onloadattributo. Inoltre, ciò richiede l'esistenza e il caricamento di un file aggiuntivo. La soluzione di momo è meno di un compromesso.
fregante

15
Potresti Base64 codificare la tua immagine trigger come <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">(questo non farà una richiesta di rete) In realtà ... NON hai bisogno di un'immagine, fai riferimento a un'immagine inesistente e invece di onloadusarla onerror(ma questo farà una richiesta di rete)
Danny Engelman "365CSI",

83

Ecco un metodo che sostituisce in modo ricorsivo tutti gli script con quelli eseguibili:

function nodeScriptReplace(node) {
        if ( nodeScriptIs(node) === true ) {
                node.parentNode.replaceChild( nodeScriptClone(node) , node );
        }
        else {
                var i        = 0;
                var children = node.childNodes;
                while ( i < children.length ) {
                        nodeScriptReplace( children[i++] );
                }
        }

        return node;
}
function nodeScriptIs(node) {
        return node.tagName === 'SCRIPT';
}
function nodeScriptClone(node){
        var script  = document.createElement("script");
        script.text = node.innerHTML;
        for( var i = node.attributes.length-1; i >= 0; i-- ) {
                script.setAttribute( node.attributes[i].name, node.attributes[i].value );
        }
        return script;
}

Esempio di chiamata:

nodeScriptReplace(document.getElementsByTagName("body")[0]);

9
Sono un po 'sorpreso che la tua risposta sia finita. IMHO, questa è la soluzione migliore, questo metodo ti permetterebbe persino di limitare gli script con URL o contenuti specifici.
davidmh,

1
@ inf3rno fa o no? Funzionava, qualcuno ha mai reclamato qualcosa di diverso?
mmm

quali sono gli scopi di [0]? puoi usare nodeScriptReplace (document.getElementById (). html);
Bao Thai,

@BaoThai Sì. Puoi.
mmm

Non sembra aiutare in IWebBrowser2; Posso confermare che i tag di script vengono ricreati con createElement, ma non riesco ancora a invocarli tramite InvokeScript ().
Dave,

46

È possibile creare uno script e quindi iniettare il contenuto.

var g = document.createElement('script');
var s = document.getElementsByTagName('script')[0];
g.text = "alert(\"hi\");"
s.parentNode.insertBefore(g, s);

Funziona con tutti i browser :)


1
A meno che non ci siano altri elementi di script nel documento. Usa document.documentElementinvece.
Eli Grey,

4
Non è necessario perché stai scrivendo una sceneggiatura da un'altra sceneggiatura. <script> var g = document.createElement('script'); var s = document.getElementsByTagName('script')[0]; //reference this script g.text = "alert(\"hi\");" s.parentNode.insertBefore(g, s); </script>
Pablo Moretti,

3
Chi dice che provenga da un'altra sceneggiatura? Puoi eseguire JavaScript senza <script>elementi. Ad esempio, <img onerror="..." src="#">e <body onload="...">. Se vuoi essere tecnico, questo non funzionerà nei documenti non HTML / SVG sia a causa dello spazio dei nomi esplicito.
Eli Gray,

2
Facebook utilizza la risposta di Pablo nel proprio SDK. developers.facebook.com/docs/javascript/quickstart/v2.2#loading
geoyws

30

Ho usato questo codice, funziona benissimo

var arr = MyDiv.getElementsByTagName('script')
for (var n = 0; n < arr.length; n++)
    eval(arr[n].innerHTML)//run script inside div

1
Grazie. Risolto il mio problema con l'aggiunta del codice Disqus Universal a un popup modale creato usando il plugin TinyBox2 Jquery.
gsinha,

3
Sfortunatamente, questa soluzione non funziona quando lo script contiene funzioni che verranno invocate in seguito.
Jose Gómez,

7

Ho avuto questo problema con innerHTML, ho dovuto aggiungere uno script Hotjar al tag "head" della mia applicazione Reactjs e avrebbe dovuto essere eseguito subito dopo l'aggiunta.

Una delle buone soluzioni per l'importazione dinamica dei nodi nel tag "head" è il modulo React-helment .


Inoltre, esiste una soluzione utile per il problema proposto:

Nessun tag di script in innerHTML!

Si scopre che HTML5 non consente l'aggiunta dinamica di tag di script tramite la proprietà innerHTML. Quindi il seguente non verrà eseguito e non ci sarà alcun avviso che dice Hello World!

element.innerHTML = "<script>alert('Hello World!')</script>";

Questo è documentato nelle specifiche HTML5:

Nota: gli elementi di script inseriti tramite innerHTML non vengono eseguiti quando vengono inseriti.

Ma attenzione, questo non significa che innerHTML sia al sicuro dagli script tra siti. È possibile eseguire JavaScript tramite innerHTML senza utilizzare i tag, come illustrato nella pagina innerHTML di MDN .

Soluzione: aggiunta dinamica di script

Per aggiungere dinamicamente un tag di script, è necessario creare un nuovo elemento di script e aggiungerlo all'elemento target.

Puoi farlo per gli script esterni:

var newScript = document.createElement("script");
newScript.src = "http://www.example.com/my-script.js";
target.appendChild(newScript);

E script in linea:

var newScript = document.createElement("script");
var inlineScript = document.createTextNode("alert('Hello World!');");
newScript.appendChild(inlineScript); 
target.appendChild(newScript);

5

Per chiunque innerHTMLstia ancora tentando di farlo, no, non è possibile iniettare uno script usando , ma è possibile caricare una stringa in un tag di script usando un Blobe URL.createObjectURL.

Ho creato un esempio che ti consente di eseguire una stringa come script e di ottenere le "esportazioni" dello script tramite una promessa:

function loadScript(scriptContent, moduleId) {
    // create the script tag
    var scriptElement = document.createElement('SCRIPT');

    // create a promise which will resolve to the script's 'exports'
    // (i.e., the value returned by the script)
    var promise = new Promise(function(resolve) {
        scriptElement.onload = function() {
            var exports = window["__loadScript_exports_" + moduleId];
            delete window["__loadScript_exports_" + moduleId];
            resolve(exports);
        }
    });

    // wrap the script contents to expose exports through a special property
    // the promise will access the exports this way
    var wrappedScriptContent =
        "(function() { window['__loadScript_exports_" + moduleId + "'] = " + 
        scriptContent + "})()";

    // create a blob from the wrapped script content
    var scriptBlob = new Blob([wrappedScriptContent], {type: 'text/javascript'});

    // set the id attribute
    scriptElement.id = "__loadScript_module_" + moduleId;

    // set the src attribute to the blob's object url 
    // (this is the part that makes it work)
    scriptElement.src = URL.createObjectURL(scriptBlob);

    // append the script element
    document.body.appendChild(scriptElement);

    // return the promise, which will resolve to the script's exports
    return promise;
}

...

function doTheThing() {
    // no evals
    loadScript('5 + 5').then(function(exports) {
         // should log 10
        console.log(exports)
    });
}

L'ho semplificato dalla mia effettiva implementazione, quindi nessuna promessa che non ci siano bug. Ma il principio funziona.

Se non ti interessa recuperare alcun valore dopo l'esecuzione dello script, è ancora più semplice; lascia fuori i bit Promisee onload. Non è nemmeno necessario avvolgere lo script o creare la window.__load_script_exports_proprietà globale .


1
L'ho appena provato e funziona su Chrome 57. innerHTML su un tag script esegue il testo.
iPherian il

È interessante, non ha funzionato prima. Mi chiedo se questo comportamento sia cross-browser o solo in Chrome 57.
JayArby,

4

Ecco una funzione ricorsiva per impostare innerHTML di un elemento che utilizzo nel nostro ad server:

// o: container to set the innerHTML
// html: html text to set.
// clear: if true, the container is cleared first (children removed)
function setHTML(o, html, clear) {
    if (clear) o.innerHTML = "";

    // Generate a parseable object with the html:
    var dv = document.createElement("div");
    dv.innerHTML = html;

    // Handle edge case where innerHTML contains no tags, just text:
    if (dv.children.length===0){ o.innerHTML = html; return; }

    for (var i = 0; i < dv.children.length; i++) {
        var c = dv.children[i];

        // n: new node with the same type as c
        var n = document.createElement(c.nodeName);

        // copy all attributes from c to n
        for (var j = 0; j < c.attributes.length; j++)
            n.setAttribute(c.attributes[j].nodeName, c.attributes[j].nodeValue);

        // If current node is a leaf, just copy the appropriate property (text or innerHTML)
        if (c.children.length == 0)
        {
            switch (c.nodeName)
            {
                case "SCRIPT":
                    if (c.text) n.text = c.text;
                    break;
                default:
                    if (c.innerHTML) n.innerHTML = c.innerHTML;
                    break;
            }
        }
        // If current node has sub nodes, call itself recursively:
        else setHTML(n, c.innerHTML, false);
        o.appendChild(n);
    }
}

Puoi vedere la demo qui .


3

Potresti farlo in questo modo:

var mydiv = document.getElementById("mydiv");
var content = "<script>alert(\"hi\");<\/script>";

mydiv.innerHTML = content;
var scripts = mydiv.getElementsByTagName("script");
for (var i = 0; i < scripts.length; i++) {
    eval(scripts[i].innerText);
}

3

Qui una soluzione che non utilizza evale funziona con script , script collegati e moduli .

La funzione accetta 3 parametri:

  • html : stringa con il codice html da inserire
  • dest : riferimento all'elemento target
  • append : flag booleano per abilitare l'aggiunta alla fine dell'elemento target html
function insertHTML(html, dest, append=false){
    // if no append is requested, clear the target element
    if(!append) dest.innerHTML = '';
    // create a temporary container and insert provided HTML code
    let container = document.createElement('div');
    container.innerHTML = html;
    // cache a reference to all the scripts in the container
    let scripts = container.querySelectorAll('script');
    // get all child elements and clone them in the target element
    let nodes = container.childNodes;
    for( let i=0; i< nodes.length; i++) dest.appendChild( nodes[i].cloneNode(true) );
    // force the found scripts to execute...
    for( let i=0; i< scripts.length; i++){
        let script = document.createElement('script');
        script.type = scripts[i].type || 'text/javascript';
        if( scripts[i].hasAttribute('src') ) script.src = scripts[i].src;
        script.innerHTML = scripts[i].innerHTML;
        document.head.appendChild(script);
        document.head.removeChild(script);
    }
    // done!
    return true;
}

Voglio dire ... L'aggiunta di un tag di script con contenuto di codice è una valutazione, non è vero?
Kevin B,

@KevinB Ci sono differenze note ... prova eval('console.log(this)')e vedrai la più ovvia
colxi

quindi il contesto è diverso e? è ancora solo una valutazione.
Kevin B,

@KevinB No, non è un giudizio. Prova questo eval('let b=100').. e poi prova ad accedere bdall'esterno della valutazione .... buona fortuna, ne avrai bisogno
colxi

Per me va bene. Saluti
Bezzzo,

1

Krasimir Tsonev ha un'ottima soluzione che supera tutti i problemi. Il suo metodo non ha bisogno di usare eval, quindi non esistono problemi di prestazioni o sicurezza. Ti permette di impostare la stringa innerHTML che contiene HTML con js e tradurlo immediatamente in un elemento DOM mentre esegue anche le parti js presenti lungo il codice. breve, semplice e funziona esattamente come vuoi tu.

Goditi la sua soluzione:

http://krasimirtsonev.com/blog/article/Convert-HTML-string-to-DOM-element

Note importanti:

  1. Devi avvolgere l'elemento target con il tag div
  2. Devi avvolgere la stringa src con il tag div.
  3. Se si scrive direttamente la stringa src e include parti js, prestare attenzione a scrivere correttamente i tag di script di chiusura (con \ before /) poiché si tratta di una stringa.

1

Usa $(parent).html(code)invece di parent.innerHTML = code.

Di seguito vengono inoltre corretti gli script che utilizzano document.writee gli script caricati tramite l' srcattributo. Sfortunatamente anche questo non funziona con gli script di Google AdSense.

var oldDocumentWrite = document.write;
var oldDocumentWriteln = document.writeln;
try {
    document.write = function(code) {
        $(parent).append(code);
    }
    document.writeln = function(code) {
        document.write(code + "<br/>");
    }
    $(parent).html(html); 
} finally {
    $(window).load(function() {
        document.write = oldDocumentWrite
        document.writeln = oldDocumentWriteln
    })
}

fonte


1
un po 'tardi qui, ma chiunque stia usando questo metodo, nota che in JQuery devi caricare i tuoi script usando $ .loadScript (url) anziché <script src = "url> </script> - quest'ultimo causerà un deprecato errore XMLHttpRequest sincrono sui browser.
Stavm

1

Prova a utilizzare template e document.importNode. Ecco un esempio:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Sample</title>
</head>
<body>
<h1 id="hello_world">Sample</h1>
<script type="text/javascript">
 var div = document.createElement("div");
  var t = document.createElement('template');
  t.innerHTML =  "Check Console tab for javascript output: Hello world!!!<br/><script type='text/javascript' >console.log('Hello world!!!');<\/script>";
  
  for (var i=0; i < t.content.childNodes.length; i++){
    var node = document.importNode(t.content.childNodes[i], true);
    div.appendChild(node);
  }
 document.body.appendChild(div);
</script>
 
</body>
</html>


1
Questo non funziona con Microsoft Edge, qualche altra soluzione alternativa?
Soul

1

Puoi anche avvolgere il tuo in <script>questo modo e verrà eseguito:

<your target node>.innerHTML = '<iframe srcdoc="<script>alert(top.document.title);</script>"></iframe>';

Nota: l'ambito all'interno si srcdocriferisce all'iframe, quindi è necessario utilizzare topcome nell'esempio sopra per accedere al documento principale.


0

Sì, puoi, ma devi farlo al di fuori del DOM e l'ordine deve essere giusto.

var scr = '<scr'+'ipt>alert("foo")</scr'+'ipt>';
window.onload = function(){
    var n = document.createElement("div");
    n.innerHTML = scr;
    document.body.appendChild(n);
}

... avviserà "pippo". Questo non funzionerà:

document.getElementById("myDiv").innerHTML = scr;

E anche questo non funzionerà, perché il nodo viene inserito per primo:

var scr = '<scr'+'ipt>alert("foo")</scr'+'ipt>';
window.onload = function(){
    var n = document.createElement("div");
    document.body.appendChild(n);
    n.innerHTML = scr;  
}

16
Per quello che vale: questo non sembra funzionare sui browser attuali.
Wichert Akkerman,

0

La mia soluzione a questo problema è impostare un osservatore di mutazioni per rilevare i <script></script>nodi e quindi sostituirlo con un nuovo <script></script>nodo con lo stesso SRC. Per esempio:

let parentNode = /* node to observe */ void 0
let observer = new MutationObserver(mutations=>{
    mutations.map(mutation=>{
        Array.from(mutation.addedNodes).map(node=>{
            if ( node.parentNode == parentNode ) {
                let scripts = node.getElementsByTagName('script')
                Array.from(scripts).map(script=>{
                    let src = script.src
                    script = document.createElement('script')
                    script.src = src
                    return script
                })
            }
        })
    })
})
observer.observe(document.body, {childList: true, subtree: true});

1
Grazie per avermi votato senza dire perché. Vi amo tutti.
gabriel garcia,

0

La menzione di Gabriel Garcia su MutationObservers è sulla buona strada, ma non ha funzionato del tutto per me. Non sono sicuro che ciò fosse dovuto a una stranezza del browser o a un errore da parte mia, ma la versione che ha funzionato per me è stata la seguente:

document.addEventListener("DOMContentLoaded", function(event) {
    var observer = new MutationObserver(mutations=>{
        mutations.map(mutation=>{
            Array.from(mutation.addedNodes).map(node=>{
                if (node.tagName === "SCRIPT") {
                    var s = document.createElement("script");
                    s.text=node.text;
                    if (typeof(node.parentElement.added) === 'undefined')
                        node.parentElement.added = [];
                    node.parentElement.added[node.parentElement.added.length] = s;
                    node.parentElement.removeChild(node);
                    document.head.appendChild(s);
                }
            })
        })
    })
    observer.observe(document.getElementById("element_to_watch"), {childList: true, subtree: true,attributes: false});
};

Ovviamente, dovresti sostituirlo element_to_watchcon il nome dell'elemento che viene modificato.

node.parentElement.addedviene utilizzato per archiviare i tag di script aggiunti document.head. Nella funzione utilizzata per caricare la pagina esterna, è possibile utilizzare qualcosa di simile al seguente per rimuovere i tag di script non più rilevanti:

function freeScripts(node){
    if (node === null)
        return;
    if (typeof(node.added) === 'object') {
        for (var script in node.added) {
            document.head.removeChild(node.added[script]);
        }
        node.added = {};
    }
    for (var child in node.children) {
        freeScripts(node.children[child]);
    }
}

E un esempio dell'inizio di una funzione di caricamento:

function load(url, id, replace) {
    if (document.getElementById(id) === null) {
        console.error("Element of ID "+id + " does not exist!");
        return;
    }
    freeScripts(document.getElementById(id));
    var xhttp = new XMLHttpRequest();
    // proceed to load in the page and modify innerHTML
}

Noti che stai aggiungendo un nuovo MutationObserverogni volta che un elemento viene aggiunto al documento, giusto? A proposito, mi chiedo perché dici che il mio codice non funziona.
gabriel garcia,

@gabrielgarcia Ho detto che il tuo codice non funzionava perché l'ho provato e semplicemente non ha funzionato. Guardandolo ora, è del tutto possibile che fosse su di me, non su di te, e mi scuso sinceramente per il modo in cui l'ho espresso. Risolvendolo ora.
pixelherodev,

ri: aggiungendo un MutationObserver ogni volta che un elemento viene aggiunto al documento, di cosa stai parlando? DOMContentLoaded, e cito qui da MDN, "viene generato quando il documento HTML iniziale è stato completamente caricato e analizzato, senza attendere il completamento del caricamento di fogli di stile, immagini e sottoframe". Questa è una volta, e una volta sola. Inoltre, questo script funziona senza problemi sul mio sito e il debug mostra che sta accadendo solo una volta, quindi è una volta sia in pratica che in teoria.
pixelherodev,

1
hai ragione ... l'ho sbagliato. Mi scuso pure.
gabriel garcia,

@gabrielgarcia Not a problem :)
pixelherodev

0

Per me il modo migliore è inserire il nuovo contenuto HTML tramite innerHtml e quindi utilizzarlo

setTimeout(() => {
        var script_el = document.createElement("script")
        script_el.src = 'script-to-add.js'
        document.body.appendChild(script_el)
    }, 500)

SetTimeout non è richiesto ma funziona meglio. Questo ha funzionato per me.


-1

Eseguire il tag (Java Script) da innerHTML

Sostituisci l'elemento di script con div con un attributo class class = "javascript" e chiudilo con </div>

Non modificare il contenuto che si desidera eseguire (in precedenza era nel tag script e ora è nel tag div)

Aggiungi uno stile alla tua pagina ...

<style type="text/css"> .javascript { display: none; } </style>

Ora esegui eval usando jquery (Jquery js dovrebbe essere già incluso)

   $('.javascript').each(function() {
      eval($(this).text());

    });`

Puoi esplorare di più qui , sul mio blog.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.