Bisakah skrip dimasukkan dengan innerHTML?


223

Saya mencoba memuat beberapa skrip ke dalam halaman menggunakan innerHTMLdi a <div>. Tampaknya skrip dimuat ke DOM, tetapi tidak pernah dieksekusi (setidaknya di Firefox dan Chrome). Apakah ada cara agar skrip dijalankan saat memasukkannya innerHTML?

Kode sampel:

<!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>

Jawaban:


88

Anda harus menggunakan eval () untuk menjalankan kode skrip apa pun yang telah Anda masukkan sebagai teks DOM.

Mootools akan melakukan ini untuk Anda secara otomatis, dan saya yakin jQuery juga akan (tergantung pada versi. JQuery versi 1.6+ menggunakan eval). Ini menghemat banyak kerumitan untuk mengurai <script>tag dan melarikan diri dari konten Anda, serta banyak "gotcha" lainnya.

Umumnya jika Anda akan melakukannya eval()sendiri, Anda ingin membuat / mengirim kode skrip tanpa markup HTML seperti <script>, karena ini tidak akan eval()benar.


12
Yang benar-benar ingin saya lakukan adalah memuat skrip eksternal, bukan hanya eval beberapa skrip lokal. Menambahkan tag skrip dengan innerHTML jauh lebih pendek daripada membuat elemen skrip DOM dan menambahkannya ke badan, dan saya mencoba membuat kode saya sesingkat mungkin. Apakah Anda harus membuat elemen skrip dom dan menambahkannya ke dom daripada hanya menggunakan sesuatu seperti innerHTML? Apakah ada cara untuk melakukan ini dengan document.write dari dalam suatu fungsi?
Craig

5
Seperti yang disarankan zombat, gunakan kerangka kerja Javascript untuk memuat skrip eksternal, jangan mencoba untuk menemukan kembali roda. JQuery menjadikan ini sangat mudah, cukup sertakan JQuery dan panggil: $ .getScript (url). Anda juga dapat menyediakan fungsi panggilan balik yang akan dieksekusi setelah skrip dimuat.
Ariel Popovsky

2
Ariel benar. Saya menghargai upaya menjaga kode Anda tetap pendek, dan menambahkan <script>tag innerHTMLmungkin pendek, tetapi tidak berhasil. Itu semua hanya teks biasa sampai dijalankan eval(). Dan sayangnya, eval()tidak mem-parsing tag HTML, jadi Anda berakhir dengan serangkaian masalah.
zombat

21
eval () bukan solusi yang bagus untuk masalah apa pun.
buley

2
Saya mencoba eval () sendiri. Itu ide yang mengerikan. Anda harus mengevaluasi semuanya. SETIAP SAAT . Bahkan jika Anda mendeklarasikan nama variabel dan nilai, Anda harus mendeklarasikan ulang / re-eval () setiap kali kembali untuk membuatnya berfungsi. Ini adalah mimpi buruk kesalahan.
Youstay Igo

88

Berikut ini solusi yang sangat menarik untuk masalah Anda: http://24ways.org/2005/have-your-dom-and-script-it-too

Jadi gunakan ini sebagai ganti tag skrip:

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


12
Ini brilian!
confile

Tidak berfungsi untuk saya, ketika dimasukkan ke dalam hasil permintaan ajax: Kesalahan sintaksis hilang; sebelum pernyataan di awal naskah skrip
Oliver

Bagaimana Anda menambahkan baris & lt; img src ... ke kode halaman Anda? Apakah Anda mendokumentasikan.write () atau menggunakan document.body.innerHTML + = pendekatan untuk itu? Keduanya gagal untuk saya :(
Youstay Igo

Tidak terlalu praktis untuk menulis banyak kode di dalam onloadatribut. Juga ini membutuhkan file tambahan untuk ada dan dimuat. solusi momo kurang kompromi.
fregante

15
Anda dapat Base64 menyandikan gambar pemicu Anda sebagai <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">(ini tidak akan melakukan permintaan jaringan) Sebenarnya ... Anda TIDAK membutuhkan gambar, referensi gambar yang tidak ada dan alih-alih onloadmenggunakan onerror(tapi ini akan melakukan permintaan jaringan)
Danny '365CSI' Engelman

83

Berikut adalah metode yang secara rekursif mengganti semua skrip dengan skrip yang dapat dieksekusi:

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;
}

Contoh panggilan:

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

9
Saya agak terkejut bahwa jawaban Anda semuanya turun. IMHO, ini adalah solusi terbaik, metode ini bahkan akan memungkinkan Anda untuk membatasi skrip dengan url atau konten tertentu.
davidmh

1
@ inf3rno apakah atau tidak? Dulu bekerja, ada yang pernah mengklaim sesuatu yang berbeda?
mmm

apa tujuan dari [0]? dapatkah Anda menggunakan nodeScriptReplace (document.getElementById (). html);
Bao Thai

@ BaoThai Ya. Kamu bisa.
mmm

Tampaknya tidak membantu di IWebBrowser2; Saya dapat mengonfirmasi bahwa tag skrip dibuat ulang dengan createElement, tetapi saya masih tidak dapat memohonnya melalui InvokeScript ().
Dave

46

Anda dapat membuat skrip lalu menyuntikkan konten.

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

Ini bekerja di semua browser :)


1
Kecuali jika tidak ada elemen skrip lain dalam dokumen. Gunakan document.documentElementsebagai gantinya.
Eli Gray

4
Tidak perlu karena Anda sedang menulis skrip dari skrip lain. <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
Siapa bilang itu dari skrip lain? Anda dapat menjalankan JavaScript tanpa <script>elemen. Misalnya <img onerror="..." src="#">dan <body onload="...">. Jika Anda ingin teknis, ini tidak akan berfungsi dalam dokumen non-HTML / SVG karena penempatan nama yang tidak jelas.
Eli Grey

2
Facebook menggunakan jawaban Pablo di SDK mereka. developers.facebook.com/docs/javascript/quickstart/v2.2#loading
geoyws

30

Saya menggunakan kode ini, itu berfungsi dengan baik

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

1
Terima kasih. Itu memperbaiki masalah saya menambahkan kode Disqus Universal ke modal popup yang dibuat menggunakan plugin TinyBox2 Jquery.
gsinha

3
Sayangnya, solusi ini tidak berfungsi ketika skrip berisi fungsi yang akan dipanggil nanti.
Jose Gómez

7

Saya punya masalah dengan innerHTML ini, saya harus menambahkan skrip Hotjar ke tag "head" aplikasi Reactjs saya dan harus dijalankan segera setelah menambahkan.

Salah satu solusi bagus untuk impor Node dinamis ke tag "head" adalah modul React-helment .


Juga, ada solusi yang berguna untuk masalah yang diusulkan:

Tidak ada tag skrip di innerHTML!

Ternyata HTML5 tidak memungkinkan tag skrip ditambahkan secara dinamis menggunakan properti innerHTML. Jadi yang berikut tidak akan dieksekusi dan tidak akan ada peringatan yang mengatakan Hello World!

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

Ini didokumentasikan dalam spesifikasi HTML5:

Catatan: elemen skrip yang dimasukkan menggunakan innerHTML tidak dieksekusi ketika mereka disisipkan.

Namun berhati-hatilah, ini tidak berarti innerHTML aman dari skrip lintas situs. Dimungkinkan untuk menjalankan JavaScript melalui innerHTML tanpa menggunakan tag seperti yang diilustrasikan pada halaman innerHTML MDN .

Solusi: Menambahkan skrip secara dinamis

Untuk menambahkan tag skrip secara dinamis, Anda perlu membuat elemen skrip baru dan menambahkannya ke elemen target.

Anda dapat melakukan ini untuk skrip eksternal:

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

Dan skrip sebaris:

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

5

Bagi siapa pun yang masih mencoba melakukan ini, tidak, Anda tidak dapat menyuntikkan skrip menggunakan innerHTML, tetapi dimungkinkan untuk memuat string ke tag skrip menggunakan a Blobdan URL.createObjectURL.

Saya telah membuat contoh yang memungkinkan Anda menjalankan string sebagai skrip dan mendapatkan 'ekspor' skrip dikembalikan melalui janji:

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)
    });
}

Saya telah menyederhanakan ini dari implementasi saya yang sebenarnya, jadi tidak ada janji bahwa tidak ada bug di dalamnya. Tapi prinsipnya berhasil.

Jika Anda tidak peduli untuk mendapatkan nilai kembali setelah skrip berjalan, itu bahkan lebih mudah; hanya meninggalkan keluar Promisedan onloadbit. Anda bahkan tidak perlu membungkus skrip atau membuat window.__load_script_exports_properti global .


1
Saya baru saja mencobanya dan berfungsi pada chrome 57. innerHTML pada tag script mengeksekusi teks.
iPherian

Itu menarik, tidak berhasil sebelumnya. Saya ingin tahu apakah perilaku ini lintas-browser atau hanya di chrome 57.
JayArby

4

Berikut ini adalah fungsi rekursif untuk mengatur innerHTML elemen yang saya gunakan di server iklan kami:

// 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);
    }
}

Anda dapat melihat demo di sini .


3

Anda bisa melakukannya seperti ini:

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

Di sini solusi yang tidak digunakan eval, dan bekerja dengan skrip , skrip yang ditautkan , serta dengan modul .

Fungsi menerima 3 parameter:

  • html : String dengan kode html untuk dimasukkan
  • dest : referensi ke elemen target
  • append : boolean flag untuk mengaktifkan penambahan di akhir elemen 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;
}

Maksud saya ... Menambahkan tag skrip dengan konten kode adalah eval, bukan?
Kevin B

@KevinB Ada perbedaan terkenal ... coba eval('console.log(this)')dan Anda akan melihat yang paling jelas
colxi

jadi konteksnya berbeda, dan? itu masih hanya sebuah eval.
Kevin B

@ KevinB Tidak, ini bukan eval. Coba ini eval('let b=100').. lalu coba akses bdari luar eval .... semoga berhasil, Anda akan membutuhkannya
colxi

Bekerja untukku. Cheers
Bezzzo

1

Krasimir Tsonev memiliki solusi hebat yang mengatasi semua masalah. Metodenya tidak perlu menggunakan eval, jadi tidak ada masalah kinerja atau keamanan. Ini memungkinkan Anda untuk mengatur string innerHTML berisi html dengan js dan menerjemahkannya segera ke elemen DOM sementara juga mengeksekusi bagian js yang ada di sepanjang kode. pendek, sederhana, dan berfungsi persis seperti yang Anda inginkan.

Nikmati solusinya:

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

Catatan penting:

  1. Anda perlu membungkus elemen target dengan tag div
  2. Anda perlu membungkus string src dengan tag div.
  3. Jika Anda menulis string src secara langsung dan itu termasuk bagian js, harap perhatikan untuk menulis tag skrip penutup dengan benar (dengan \ sebelum /) karena ini adalah string.

1

Gunakan $(parent).html(code)sebagai ganti parent.innerHTML = code.

Berikut ini juga memperbaiki skrip yang menggunakan document.writedan skrip dimuat melalui srcatribut. Sayangnya bahkan ini tidak berfungsi dengan skrip 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
    })
}

Sumber


1
agak terlambat di sini, tetapi siapa pun yang mungkin menggunakan metode ini, perhatikan bahwa di JQuery Anda perlu memuat skrip Anda menggunakan $ .loadScript (url) daripada <script src = "url> </script> - yang terakhir akan menyebabkan Kesalahan XMLHttpRequest Synchronous yang sudah tidak digunakan lagi pada browser
Stavm

1

Coba gunakan template dan document.importNode. Berikut ini sebuah contoh:

<!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
Ini tidak bekerja dengan Microsoft Edge, ada solusi lain?
Soul

1

Anda juga dapat membungkus Anda <script>seperti ini dan itu akan dieksekusi:

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

Harap dicatat: Ruang lingkup di dalam srcdocmengacu pada iframe, jadi Anda harus menggunakan topseperti pada contoh di atas untuk mengakses dokumen induk.


0

Ya Anda bisa, tetapi Anda harus melakukannya di luar DOM dan pesanannya harus benar.

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

... akan mengingatkan 'foo'. Ini tidak akan berfungsi:

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

Dan bahkan ini tidak akan berhasil, karena simpulnya dimasukkan terlebih dahulu:

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

16
Untuk apa nilainya: ini tampaknya tidak berfungsi di browser saat ini.
Wichert Akkerman

0

Solusi saya untuk masalah ini adalah mengatur Pengamat Mutasi untuk mendeteksi <script></script>node dan kemudian menggantinya dengan <script></script>node baru dengan src yang sama. Sebagai contoh:

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
Terima kasih telah menurunkan saya tanpa mengatakan alasannya. Cintai kamu semua.
gabriel garcia

0

Penyebutan Gabriel Garcia tentang MutationObservers ada di jalur yang benar, tetapi tidak berhasil bagi saya. Saya tidak yakin apakah itu karena kekhasan browser atau karena kesalahan pada saya, tetapi versi yang akhirnya bekerja untuk saya adalah sebagai berikut:

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});
};

Tentu saja, Anda harus mengganti element_to_watchdengan nama elemen yang sedang dimodifikasi.

node.parentElement.addeddigunakan untuk menyimpan tag skrip yang ditambahkan document.head. Dalam fungsi yang digunakan untuk memuat halaman eksternal, Anda dapat menggunakan sesuatu seperti berikut ini untuk menghapus tag skrip yang tidak lagi relevan:

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]);
    }
}

Dan contoh dari awal fungsi pemuatan:

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
}

Anda perhatikan bahwa Anda menambahkan baru MutationObserversetiap kali elemen ditambahkan ke dokumen, kan? Btw, saya bertanya-tanya mengapa Anda mengatakan kode saya tidak berfungsi.
gabriel garcia

@ gabrielgarcia Saya katakan kode Anda tidak berfungsi karena saya mencobanya dan itu tidak berhasil. Melihat itu sekarang, sangat mungkin itu ada pada saya, bukan Anda, dan saya dengan tulus meminta maaf atas cara saya mengutarakan ini. Perbaiki sekarang.
pixelherodev

re: menambahkan MutationObserver setiap kali elemen ditambahkan ke dokumen, apa yang Anda bicarakan? DOMContentLoaded, dan saya kutip dari MDN di sini, "menyala ketika dokumen HTML awal telah dimuat dan diurai sepenuhnya, tanpa menunggu stylesheet, gambar, dan subframe selesai dimuat." Itu hanya sekali, dan sekali saja. Selain itu, skrip ini berfungsi tanpa masalah di situs saya, dan debugging menunjukkan itu hanya terjadi sekali, jadi itu dalam praktik maupun dalam teori.
pixelherodev

1
kamu benar ... Aku salah mengartikannya. Saya juga minta maaf.
gabriel garcia

@ gabrielgarcia Tidak masalah :)
pixelherodev

0

Bagi saya cara terbaik adalah memasukkan konten HTML baru melalui innerHtml dan kemudian gunakan

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

SetTimeout tidak diperlukan tetapi berfungsi lebih baik. Ini berhasil untuk saya.


-1

Jalankan tag (Java Script) dari innerHTML

Ganti elemen skrip Anda dengan div yang memiliki atribut class class = "javascript" dan tutup dengan </div>

Jangan ubah konten yang ingin Anda jalankan (sebelumnya sudah ada di tag skrip dan sekarang sudah ada di tag div)

Tambahkan gaya di halaman Anda ...

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

Sekarang jalankan eval menggunakan jquery (Jquery js harus sudah termasuk)

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

    });`

Anda dapat menjelajahi lebih lanjut di sini , di blog saya.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.