【问题标题】:Dynamically added javascript with external script doesn't get executed使用外部脚本动态添加的 javascript 不会被执行
【发布时间】:2016-11-23 09:53:02
【问题描述】:

这就是我们的场景...我们要做的第一件事是,我们附加一段 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请求的函数,请检查编辑的代码sn-p。

编辑 2

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

我刚刚尝试在createDiv() 函数附加的脚本之一中调用console.log('hello world');,但它什么也没做。

编辑 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() 函数向文档的&lt;head&gt;&lt;/head&gt; 添加了一些CSS,它影响了我的id 为“just_id”的div。 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/"); 中的第三个参数 true(异步)和 false(同步)已经尝试过了。还是没有成功。

编辑 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

.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 from 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 和上面的例子一样

第二张图是script.js的源代码,正在192.168.2.197/testing/whatever/etc上加载。 .htaccess 和上面的例子一样:

第三张图是inspector的截图,我的dom是什么样子的:

第四张图片....什么也没有发生。只是一个与我的问题无关的警告(我希望如此),因为即使我删除了所有脚本,它仍然会出现。

它是 Firefox 的错误吗?

【问题讨论】:

  • 很可能是由于Same-origin policy
  • @hindmost 但这应该会给出警告/错误消息。
  • 语法错误的错误消息仅适用于同源脚本。 (source)
  • 如果脚本中有 syntax 错误,你是对的。但是当由于同源策略而无法加载脚本时,它仍然会显示警告/错误消息。
  • @kso:您展示的示例脚本只是定义函数,但它从未真正调用它们。

标签: javascript html dom


【解决方案1】:

问题是上面的代码混合了appendChildinnerHTML。在某些情况下,两者的表现不同并不直观。前者允许新插入的脚本节点在附加到 DOM 时执行。后者没有。这让很多人头疼不已。请谷歌“script and 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 的结果,表明这是有效的。

【讨论】:

  • 感谢您的回答,但您没有检查编辑历史记录。如果您查看第 3 次编辑和第 5 次编辑,您会看到我开始添加内联脚本以进行测试。我怎么能执行外部脚本?这适用于您向我们展示的内联脚本,但实际问题在于外部脚本,具有 src 属性的脚本标签。而且我们还必须创建s1s2 等元素,从其他文档内联脚本中附加innerHTML,然后将元素附加到文档中,但是如果我直接从其他文档中附加它们,它就不起作用.
  • @ksno 我拿走了你的代码并在我的服务器上运行它(我用截图更新了我的答案)。外部脚本在我进行修改时执行,包括内联脚本和外部脚本,并且没有 COORS 问题,因为这本质上是 JSONP,只要初始 ajax GET 是您控制的域或启用了 COORS。
  • 如果 1.initial 脚本运行在假设 192.168.1.194/index.html 2.loads script from localhost/js/script.js 3.script.js 从 localhost/js2/script.js 加载脚本有区别吗? 192.168.1.194 和 localhost 是同一台机器。我真的不明白为什么它可以在您的服务器上运行,而在这里却不行。是的,在localhost 上启用了 COORS。我第一次在控制台中遇到 COORS 错误,所以我修复了它。
  • 脚本 1 到 3 可以来自任何地方,任何域,因为它们不受 CORS 的约束(它是 CORS,而不是我在上面的评论中打错的 COORS)。只有 script.js 受 CORS 限制。你有其他代码吗? Htaccess规则?你运行的是什么浏览器?应该没什么区别,但可能有一个值得检查的错误报告。
  • 192..... 只是一个简单的 wordpress 页面,而 localhost 是......我们称之为自制框架。 htaccess 更新来了我的问题。检查编辑 10.
【解决方案2】:

页面的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;
};

【讨论】:

  • 是的,已经考虑过了,它有利于内联脚本执行。但是,不幸的是,响应是 html 而不是 js,并且有几个外部脚本,这会导致我收到多个 ajax 请求。
【解决方案3】:

问题是您的方法是异步而不是同步执行。这意味着在您插入动态脚本时页面仍在加载。 有几种方法可以做到这一点:

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

  2. 你也可以使用 jQuery $.getScript.

  3. 1234563太好了,想个更流畅的方法。
  4. 使用XMLHTTPRequest 检索您的脚本。这是同步的,我可以看出您对它很熟悉,因为您已经在脚本中使用了它。

祝你好运,编码愉快。

【讨论】:

  • 问题不是关于jQuery,而是纯粹的JS
【解决方案4】:

您是否尝试过获取 src 文件的 URL 并使用 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();

【讨论】:

  • 问题不是关于jQuery,而是纯粹的JS
【解决方案5】:

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

所以...基本上你有正确的函数定义和一切,稍后从控制台调用它们应该可以工作(我认为你已经尝试过并且很好)。

尽管如此,对您的函数的调用,即使它们在那里也不会执行,因为在加载后不会被计算。

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

【讨论】:

    【解决方案6】:

    我会说删除异步,因为该操作是在初始页面加载之后进行的

    【讨论】:

      【解决方案7】:
          (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();
      

      【讨论】:

        猜你喜欢
        • 2011-12-07
        • 1970-01-01
        • 2023-04-09
        • 1970-01-01
        • 1970-01-01
        • 2020-05-20
        • 2022-12-21
        • 2015-08-21
        • 2015-03-22
        相关资源
        最近更新 更多