动态创建的脚本标签如何知道已经执行?

14

我正在动态创建一个脚本标签:

var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.charset = 'utf-8';
script.defer = true;
script.async = true;
script.text = 'some my javascript content here';
head.appendChild(script);

script.onload = function () {
    // this never get fired..
    debugger;
}
如何在其他代码块中执行脚本时得到通知?也许可以使用某些事件?

1
script.text 中包含一个 console.log()。由于创建内联脚本时实际上没有加载任何内容,因此 script.onload 永远不会被触发。如果您将脚本保存到文件中并使用 src 加载它,则将触发 onload。为确保这一点,请在 onload 定义之后移动 appendChild() - Teemu
我需要在其他函数范围内得到通知。 - Kosmetika
1
你应该先设置onload事件,然后再将其添加到DOM中!否则,onload会在处理程序设置之前完成。 - Grim
各位,我已经创建了一个fiddle进行测试。如果有人能够在不使用'src'的情况下触发onload事件,那么我将是第一个点赞的人......但是,IE11似乎在运行fiddle时会触发'onload'事件。 - Teemu
1
@Teemu,请查看我的答案和更新后的fiddle... http://jsfiddle.net/x3L4nvk3/1/ - pherris
请查看我的答案,其中提供了一个更简单的方法,不需要涉及“load”事件。 - mindplay.dk
8个回答

10
我通过给脚本添加ID,然后在JS中手动触发该DOM元素上的load事件,使其正常工作。仅在Chrome中测试过,在较旧版本的IE中可能会出现问题(参见MDN)。
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.charset = 'utf-8';
script.id = 'testing';
script.defer = true;
script.async = true;
script.onload = function () {
    console.log('The script is loaded');
}
script.text = ["console.log('This is from the script');",
               "var script = document.getElementById('testing');",
               "var event = new UIEvent('load');",
               "script.dispatchEvent(event);"].join('');
head.appendChild(script);

代码片段


好的,似乎在FF中也能正常工作。我的回答中一个专业之处就是你根本不需要改变原始的 script.text。但是我会+1,因为我已经承诺了:)。 - Teemu

3

在现代浏览器中,您可以使用Mutation Observer来检测元素的更改 - 在此示例中是head。代码如下:

observer = new MutationObserver(function (m) {
    // This will be fired
});
observer.observe(document.head, {childList: true});

不幸的是,这在IE < 11中不起作用,但似乎onload在IE中被触发,因此您可以在IE中使用它。

在jsFiddle上查看演示.


0
该代码每100毫秒检查一次脚本是否已附加到DOM中。您可以在应用程序的任何地方使用它,无需使用事件侦听器和分派事件。 同样,您可以看到代码将抛出错误的时间间隔,如果脚本没有在您设置的时间间隔内附加,则会出现错误。
const waitForScriptToLoad = (scriptName, checkTimeMs, timeOutMs) => {
  let elapsedTime = 0;
  return new Promise((resolve, reject) => {
    setTimeout(x => reject('script: ' + scriptName + ' Timed out!')
      , timeOutMs)
    const time = setInterval(() => {
      elapsedTime += checkTimeMs;
      if (document.body.innerHTML.indexOf(scriptName) > -1) {
        resolve(
          {
            response: 'script: ' + scriptName + ' found!',
            time: (elapsedTime / 1000).toFixed(2) + 's'
          });
        clearInterval(time);
      }
    }, checkTimeMs)
  })
}

waitForScriptToLoad('script_name.js', 100, 20000)
  .then(res => console.log(res))
  .catch(err => console.log(err))

0

根据我的发现,你可以简单地这样做:

var script = document.createElement("script");

script.innerHTML = "console.log('(1) hello from inline script');"

document.head.appendChild(script);

setTimeout(
  function() {
    console.log("(2) we're done!");
  },
  0 //  zero timeout
);

为什么这个方法可行:零超时意味着基本上是“下一个事件循环”——由于它是一个内联的<script>节点,当你将它附加到<head>时,没有任何网络活动的延迟,这意味着浏览器将在下一个事件循环之后立即执行内联脚本。

只要不使用load事件,就可以放心使用。

在IE 10 + 11和当前版本的Chrome、Firefox、Edge、Opera和Safari中进行了测试并且可行。

JsFiddle

如果你的脚本需要支持内联和外部脚本,请使用类似if (script.src)的条件来测试脚本是否为外部脚本,然后有条件地使用load事件监听器或零超时。


-1
您需要在设置脚本源之前定义onload函数。然而,正如Teemu所告诉我的那样,由于您通过text属性编写javascript代码,onload事件永远不会触发。您最好的选择是拥有一个外部js文件,并通过src属性加载它。
顺序应该是:
  • 将脚本元素附加到DOM中
  • 定义onload函数
  • 定义src
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.charset = 'utf-8';
script.defer = true;
script.async = true;
head.appendChild(script);
script.onload = function () {
    // this never get fired..
    debugger;
}
script.src = 'scriptName.js';

然后您的onload事件应该触发,您可以在函数中插入一个console.log("script has loaded!");语句。


onload 没有触发,外部资源从未被加载。 - Teemu
1
哦,因为src没有被使用? - Sterling Archer

-1

另一种解决方案(不使用onloadsrc

window.onload = function(){

    var script = document.createElement('SCRIPT'),
        content = 'console.debug("Hello Kitty");'; // or console.log

    script.type = 'text/javascript';

    try {
      script.appendChild(document.createTextNode(content));
    } catch (e) {
      script.text = content;
    } finally {
      document.getElementsByTagName("HEAD")[0].appendChild(script);
    }
};

工作演示(在IE、FF、Chrome和Opera上测试过)


额...问题是:“如何在其他代码块中执行脚本时得到通知?” - Teemu
@Teemu 嗯...谢谢你指出来。我想我错过了最后一行 ;) - hex494D49

-1

将代码提取到.js文件中。

在脚本中添加script.src = 'yourjs.js';

当脚本添加到DOM时,.js文件会立即执行。

yourjs.js的顶部添加if(console){console.debug('helloworld。');},您将看到该消息。


如果您加载外部JS,只要在添加脚本之前注册了onload事件,它就会被触发,一切都可以通过仅加载外部脚本来完成... - pherris

-2

为了测试目的,您总是可以添加调用alert()函数的语句。然而,在查看您的代码后,我不确定其中是否有实际调用该函数的任何内容。"onload"事件处理程序可能应该添加到动态创建部分中,在执行appendChild()之前。

我写了一些代码来进行一些动态脚本创建,它运行良好。我的代码有两个主要函数,一个创建脚本(一堆脚本),从“.js”文件加载数据,另一个按它们的名称顺序调用这些脚本中的函数(使用“eval()”函数)。您的函数没有任何我可以看到的名称...我知道很多人不喜欢使用eval()函数,但只要它调用的唯一内容是由您完全编写的内容,那么就应该没问题。

此代码使浏览器而非Web服务器动态创建可点击链接项的菜单(每个可点击链接看起来像普通超链接,但实际上是JavaScript构造,浏览器必须启用JavaScript才能让链接工作——但是它必须启用JavaScript才能创建菜单,所以没有问题!):

 var F00, F01, F02, F03, F04, F05, F06, F07, F08, F09,
     F10, F11, F12, F13, F14, F15, F16, F17, F18, F19;
 var dat = new Array();
 var form, script, str, st2, tmp, tmp2, dtno, indx, unde;

 function initialize()
 { window.name="MyMenu";
   form = document.getElementById('MENU');
   for(indx=0; indx<20; indx++)
   { str = "0" + indx;
     tmp = str.length - 2;
     str = str.substr(tmp);
     script = document.createElement('script');
     script.type = 'text/javascript';
     script.src = str + ".js";
     form.appendChild(script);
   }
   window.setTimeout("BuildMenu();", 1000); //delay is necessary;
      // scripts are actually only loaded after the function ends,
      // and you need to allow time for it to finish
      // before calling the functions in those scripts.
   return;
 }

请注意,此代码已准备好处理20个菜单项,即使您目前只有5个菜单项可供使用。如果某些最大的“.js”文件不存在,上述函数也不会崩溃。
 function BuildMenu()
 { dtno = 0;
   for(indx=0; indx<20; indx++)
   { str = "0" + indx;
     tmp = str.length - 2;
     str = "F" + str.substr(tmp);
     tmp = eval(str);
     if(tmp != unde)  //no value is assigned to 'unde'; it is undefined;
                      //this is a valid way to find out
                      //whether or not a ".js" script existed/was-loaded.
       dat[dtno++] = eval(str + "()");
   }
   dat.sort();
   for(indx=0; indx<dtno; indx++)
   { str = "0" + indx;
     tmp = str.length - 2;
     str = "W" + str.substr(tmp);
     tmp = document.getElementById(str);
     tmp.innerHTML = "<a onclick=\"window.open('" + dat[indx][1] + "', 'MyMenu');\" style=\"color:#0000ff;text-decoration:underline;cursor:pointer;\">" + dat[indx][0] + "</a> " + dat[indx][2] + "<br />";
   }
   return;
 }

在网页的HTML代码中,有这个:
 <body onload="initialize();>
 <br />
 <form id="MENU" action="" onsubmit="return false;">
 <span id="W00">&nbsp;</span>
 <span id="W01">&nbsp;</span>
 <span id="W02">&nbsp;</span>
 <span id="W03">&nbsp;</span>
 <span id="W04">&nbsp;</span>
 <span id="W05">&nbsp;</span>
 <span id="W06">&nbsp;</span>
 <span id="W07">&nbsp;</span>
 <span id="W08">&nbsp;</span>
 <span id="W09">&nbsp;</span>
 <span id="W10">&nbsp;</span>
 <span id="W11">&nbsp;</span>
 <span id="W12">&nbsp;</span>
 <span id="W13">&nbsp;</span>
 <span id="W14">&nbsp;</span>
 <span id="W15">&nbsp;</span>
 <span id="W16">&nbsp;</span>
 <span id="W17">&nbsp;</span>
 <span id="W18">&nbsp;</span>
 <span id="W19">&nbsp;</span>
 </form>

这里是“00.js”文件的一些示例代码:

 <!--
 function F00()
 { return ["Menu Item Alpha", "./Alpha.htm", "Select Web Page Alpha"];
 }
 -->

请注意,该函数仅返回3个数组元素(字符串)。BuildMenu()函数使用这些字符串构建网页上的菜单,部分通过修改span元素的innerHTML来实现。

1
alert() 不是一个调试函数,不应该被用作调试。 - Sterling Archer
那么重点是要在脚本内容中添加一些触发器,是这样理解的吗? - Kosmetika

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接