使用外部脚本动态添加javascript并不会被执行

时间:2016-07-20 08:49:06

标签: javascript html dom

所以这是我们的场景......我们首先要做的是,我们附加一段javascript代码,将外部脚本添加到文档中:

(function() {
     var e = document.createElement('script'); e.type = 'text/javascript'; e.async = true; e.src = 'http://blabla.com/script.js';
     var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(e, s);
})();

然后在script.js发生以下情况:

function ajaxCall() { 
    //... some script to send ajax request which calls createDiv() after success
   if (window.XMLHttpRequest){
      xmlhttp=new XMLHttpRequest();
   }
   else{
      xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
   }
   xmlhttp.onreadystatechange=function(){
      if(xmlhttp.readyState==4&&xmlhttp.status==200){
         createDiv(xmlhttp.responseText);
      }
   };
   xmlhttp.open("GET","http://blabla.com/api/");
   xmlhttp.send(); 
}
function parseResponse(response) {
    var parser = new DOMParser();
    var dom = parser.parseFromString(response, "text/html");
    return dom;
}
function createDiv(responsetext)
{
   var dom = parseResponse(responsetext);

   var _ws = dom.getElementById('styles');
   var h = document.getElementsByTagName('head')[0];
   h.appendChild(document.importNode(_ws, true));

   var _jr = dom.getElementById('script1').outerHTML;
   var _vc = dom.getElementById('script2').outerHTML;
   var _rv = dom.getElementById('script3').outerHTML;

   var _rw = dom.getElementById('my_div');
   var _b = document.getElementsByTagName('body')[0];
   var _d = document.createElement('div'); _d.id = 'just_id';
   _d.innerHTML = _jr + _vc + _rv;
   _d.appendChild(document.importNode(_rw, true));
   _b.appendChild(_d);
}
ajaxCall();

一切正常,脚本和div正在按预期添加到预期的位置,但附加的脚本不会被执行并且不会影响附加的div 。这是为什么?我怎样才能让它执行?我们也没有在控制台中收到任何警告/错误。使用最新的firefox。

编辑1

评论:您展示的示例脚本只定义了函数,但它实际上从未调用它们

它实际上调用了一个发出ajax请求的函数,请检查已编辑的代码段。

编辑2

评论:您是否尝试将逻辑放入回调中,然后在createDiv中调用它?可能会添加一些日志来测试它是否被调用但无法找到div

我刚尝试console.log('hello world');调用其中一个脚本,这些脚本被createDiv()函数追加,但它什么也没做。

编辑3

了解更多详情。当页面加载时,我可以看到

<script type="text/javascript" id="script1" src="http://blabla.com/script1.js"></script>
<script type="text/javascript" id="script2" src="http://blabla.com/script2.js"></script>
<script type="text/javascript" id="script3" src="http://blabla.com/script3.js"></script>
<div id="just_id">Content here</div>

在DOM-Inspector中,如上所述,有console.log('hello world');来测试它,但它没有被执行。

编辑4

通过createDiv()功能添加了一些CSS来记录<head></head>,并且它会影响我的div,其中包含id&#39; just_id&#39;。 JS仍然没有工作。

编辑5

还尝试在我的函数中添加内联javascript而不是外部javascript,但仍然没有成功。可以在DOM-Inspector中看到以下内容,但代码没有记录任何内容。

<script type="text/javascript">
   console.log('test');
</script>

编辑6

我还尝试从suggested here

导入节点
  

不允许此方法在不同文档之间移动节点。如果要从其他文档追加节点,则必须使用document.importNode()方法。

并试图改变秩序。 1.css 2.div 3.js喜欢这个,但仍然没有成功......任何人都知道这里发生了什么?

检查上面的代码

编辑7

正如How do you execute a dynamically loaded JavaScript block?中所建议的,我将div的innerHTML设置为脚本+内容,然后像这样附加到DOM:

检查上面的代码

但仍未取得任何成功。

编辑8

添加了ajax功能代码。已尝试xmlhttp.open("GET","http://blabla.com/api/");中的第3个参数(异步)和假(同步)。仍然没有成功。

编辑9

函数createDiv(),如答案所示:

var dom = parseResponse(responsetext);

var _jr = dom.getElementById('script1'); //<script> tag with src attribute
var _vc = dom.getElementById('script2'); //inline <script> for test purposes only, it should actualy be external script also
var _rv = dom.getElementById('script3'); //inline <script>

var _rw = dom.getElementById('external_div');
var _b = document.getElementsByTagName('body')[0];
var _d = document.createElement('div'); _d.id = 'internal_div';
_d.appendChild(document.importNode(_rw, true));
_d.appendChild(document.importNode(_jr, true));
_d.appendChild(document.importNode(_rv, true)); //without second parameter TRUE, I dont get script content
_d.appendChild(document.importNode(_vc, true)); //without second parameter TRUE, I dont get script content
_b.appendChild(_d);

不起作用。只有当我从其他文档中获取内联脚本的innerHTML,创建回答中建议的元素,设置其innerHTML然后追加当前文档时,才能工作。外部脚本根本不会加载。

编辑10

来自localhost的

.htaccess ...:

<Files *.html>
    Order Deny,Allow
    Deny from all
    Allow from 127.0.0.1
</Files>

<Files *.php>
    Order Deny,Allow
    Deny from all
    Allow from 127.0.0.1
</Files>

<Files index.php>
    Order Allow,Deny
    Allow from all
</Files>

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /page1/
#RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^(.+)$ index.php?url=$1 [QSA,L]
</IfModule>

.htaccess来自192。*** ......:

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /direktexpress/
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /page2/index.php [L]
</IfModule>
# END WordPress

编辑11

所以我可能会给予赏金@Drakes,只是为了努力和他在我的问题上花费的时间,并将显示一些截图。它绝对是firefox bug,我刚刚在chrome上测试过它。

第一张图片:源代码192.168.2.197/testing/whatever/etc。 .htaccess与上面的示例相同

Source Code for 192.168.2.197

第二张图片是script.js的源代码,它正在192.168.2.197/testing/whatever/etc上加载。 .htaccess与上例中的相同:

Script.js Source

第3张图片是检查员的屏幕截图,我的dom看起来如何:

Dom

第4张图片......没有任何反应。只是一个与我的问题无关的警告(我希望如此),因为即使我删除所有脚本,它仍然会出现。

Console

这是一个firefox错误吗?

7 个答案:

答案 0 :(得分:8)

问题是上面的代码混合了appendChildinnerHTML。在某些情况下,两者的表现不同,这在直觉上并不明显。前者允许新插入的脚本节点在附加到DOM时执行。后者没有。对许多人来说,这一直是一个令人头疼的问题。请使用谷歌“脚本和innerHTML”,你会发现你并不是唯一面临这个类似问题的人。

如果你(1)改变

_d.innerHTML = _jr + _vc + _rv

_d.appendChild(document.importNode(_jr));
_d.appendChild(document.importNode(_vc));
_d.appendChild(document.importNode(_rv));

另外(2)改变

var _jr = dom.getElementById('script1').outerHTML;
var _vc = dom.getElementById('script2').outerHTML;
var _rv = dom.getElementById('script3').outerHTML;

var _jr = dom.getElementById('script1');
var _vc = dom.getElementById('script2');
var _rv = dom.getElementById('script3');

然后您的脚本将被执行。这是一个演示:

var b = document.getElementsByTagName('body')[0];

/* Works */
var s1 = document.createElement('script');
s1.innerHTML = "console.log('I will execute')";
b.appendChild(s1);

/* Fails */
var s2 = document.createElement('script');
s2.innerHTML = "while(1){alert('Do not worry! I will not execute');}";
b.innerHTML = s2.outerHTML;
console.log("It didn't execute");

<强>测试

我在我的服务器上运行OP代码以尽可能地复制问题。鉴于提供的代码,我可以使它在我的服务器上按预期工作。我只是将AJAX URL从http://blabla.com/api/更改为api.html并提供了我自己的文件,因为OP没有提供任何文件,但是我将其应该包含在OP的JavaScript中。以下是样本结果:

<强>的index.html

<html lang="en">
<body>
    <script>
        (function() {
            var e = document.createElement('script'); e.type = 'text/javascript'; e.async = true; e.src = 'script.js';
            var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(e, s);
        })();
    </script>
</body>
</html>

<强>的script.js

这是根据我的上述规范修改的OP代码(使用appendChild代替innerHTML,AJAX调用返回下面api.html的内容)

function ajaxCall() {
    //... some script to send ajax request which calls createDiv() after success
    if (window.XMLHttpRequest){
        xmlhttp=new XMLHttpRequest();
    }
    else{
        xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }
    xmlhttp.onreadystatechange=function(){
        if(xmlhttp.readyState==4&&xmlhttp.status==200){
            createDiv(xmlhttp.responseText);
        }
    };
    xmlhttp.open("GET","api.html");
    xmlhttp.send();
}
function parseResponse(response) {
    var parser = new DOMParser();
    var dom = parser.parseFromString(response, "text/html");
    return dom;
}
function createDiv(responsetext)
{
    var dom = parseResponse(responsetext);

    var _ws = dom.getElementById('styles');
    var h = document.getElementsByTagName('head')[0];
    h.appendChild(document.importNode(_ws, true));

    var _jr = dom.getElementById('script1');
    var _vc = dom.getElementById('script2');
    var _rv = dom.getElementById('script3');

    var _rw = dom.getElementById('my_div');
    var _b = document.getElementsByTagName('body')[0];
    var _d = document.createElement('div'); _d.id = 'just_id';

    _d.appendChild(document.importNode(_jr, true));
    _d.appendChild(document.importNode(_vc, true));
    _d.appendChild(document.importNode(_rv, true));

    _d.appendChild(document.importNode(_rw, true));
    _b.appendChild(_d);
}
ajaxCall();

api.html (由AJAX调用返回)

<!DOCTYPE html>
<html>
<body>
    <style type="text/css" id="styles"></style>
    <script type="text/javascript" id="script1" src="script1.js"></script>
    <script type="text/javascript" id="script2">console.log("incline script 2")</script>
    <script type="text/javascript" id="script3">console.log("incline script 3")</script>
    <div id="my_div"></div>
</body>
</html>

<强> script1.js

console.log("src script1");

以下是DevTools的结果,表明这是有效的。

Devtools inspection

Console results

答案 1 :(得分:4)

页面的JavaScript已经被解析,您需要eval将结果放入窗口上下文中。

function evalRequest(url) {
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            eval(xmlhttp.responseText);
        }
    }
    xmlhttp.open("GET", url, true);
    xmlhttp.send(null);
}; 

更新:您可能需要将脚本作为文本然后进行评估)

function getScriptsAsText() {
    var div = document.createElement('div');
    var scripts = [];
    var scriptNodes = document.getElementsByTagName('script');

    for (var i = 0, iLen = scriptNodes.length; i < iLen; i++) {
        div.appendChild(scriptNodes[i].cloneNode(true));
        scripts.push(div.innerHTML);
        div.removeChild(div.firstChild);
    }
    return scripts;
};

答案 2 :(得分:0)

您当前正在将脚本加载到页面中。这是在页面js已经执行之后发生的(因为它是异步的,并且通常加载外部资源所需的时间比脚本执行的时间长)。

所以...基本上你有正确的功能定义和所有东西,稍后从控制台调用它们应该有用(我想你已经尝试过了,并且很好)。

尽管如此,调用你的函数,甚至认为它们在那里都没有执行,因为在加载后不会计算。

要解决此问题,您可以为具有脚本的页面创建ajax请求,并在其回调中调用您需要调用的任何函数。

答案 3 :(得分:0)

我想说删除异步,因为操作在初始页面加载后出现

答案 4 :(得分:0)

    (function () {
        var s = document.getElementsByTagName('script')[0];
        s.parentNode.insertBefore(CreateScriptTag('http://blabla.com/script.js',true), s);
    })();
    function CreateScriptTag(src, isAsync) {
        var e = document.createElement('script')
        e.type = 'text/javascript';
        e.async = isAsync;
        e.src = src;
    return e;
    }
    function ajaxCall() {
        //... some script to send ajax request which calls createDiv() after success
        if (window.XMLHttpRequest) xmlhttp = new XMLHttpRequest();
        else  xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        xmlhttp.open("GET", "http://blabla.com/api/");
        xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState === 4 && xmlhttp.status === 200) createDiv(xmlhttp.responseText); };
        xmlhttp.send();
    }
    function parseResponse(response) {
        var parser = new DOMParser();
        var dom = parser.parseFromString(response, "text/html");
        return dom;
    }
    function createDiv(responsetext) {
        var head=document.getElementsByTagName('head')[0],dom = parseResponse(responsetext),_div=document.createElement('div');
        head.appendChild(document.importNode(dom.getElementById('styles'), true)); //Assuming stylesheets are working fine.
        _div.id = 'just_id';
        //div.innerHTML = _script1 + _script2 + _script3;
        document.getElementsByTagName('body')[0].appendChild(_div.appendChild(dom.getElementById('my_div')));

        head.appendChild(CreateScriptTag(dom.getElementById('script1').getAttribute("src"),true));
        head.appendChild(CreateScriptTag(dom.getElementById('script2').getAttribute("src"),true));
        head.appendChild(CreateScriptTag(dom.getElementById('script3').getAttribute("src"),true));

    /* or another idea;
        document.write(_script1 + _script2 + _script3);
    */
    }
    ajaxCall();

答案 5 :(得分:0)

问题是您的方法是异步而非同步执行。这意味着在插入动态脚本时页面仍在加载。 几种方法:

  1. 最简单的方法是使用 jQuery $(document).append代替 document.createElement,如果您不介意使用该库。

  2. 您还可以使用 jQuery $.getScript

  3. 如果您不想包含第3个库,则必须考虑在插入脚本时延迟页面加载的方法,类似alert创建提示窗口的方法可行,但用户体验不太好,想一个更顺畅的方式。

  4. 使用XMLHTTPRequest检索脚本。这是同步的,我可以看到你熟悉它,因为你已经在脚本中使用它了。

  5. 祝你好运,编码愉快。

答案 6 :(得分:0)

您是否尝试过获取src文件的网址并使用jQuery的getScript()函数将其排除?

更改creatediv()功能:

var _jr = dom.getElementById('script1').outerHTML;

var srcScript1Left = _jr.split('src="');
srcScript1Right = srcScript1Left[1].split('"');
srcScript1 = srcScript1Right[0];

console.log(srcScript1);

$.getScript(srcScript1, function()
{
    // The external script has been executed.
});

修改

纯Javascript中的类似方法:

var _jr = dom.getElementById('script1').outerHTML;

var srcScript1Left = _jr.split('src="');
srcScript1Right = srcScript1Left[1].split('"');
srcScript1 = srcScript1Right[0];

console.log(srcScript1); // Display the name of the external script file

if(window.XMLHttpRequest) {
    xmlhttp=new XMLHttpRequest();
} else {
    xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange=function() {
    if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
        eval(xmlhttp.responseText); // Execute the external script
    }
};
xmlhttp.open("GET",srcScript1);
xmlhttp.send();