document.createElement("script") 同步

IT技术 javascript dom synchronous
2021-01-29 12:25:21

是否可以.js同步调用文件然后立即使用它?

<script type="text/javascript">
    var head = document.getElementsByTagName('head').item(0);
    var script = document.createElement('script');
    script.setAttribute('type', 'text/javascript');
    script.setAttribute('src', 'http://mysite/my.js');
    head.appendChild(script);

    myFunction(); // Fails because it hasn't loaded from my.js yet.

    window.onload = function() {
        // Works most of the time but not all of the time.
        // Especially if my.js injects another script that contains myFunction().
        myFunction();
    };
</script>

这是简化的。在我的实现中,createElement 的东西在一个函数中。我想在函数中添加一些可以检查某个变量是否在返回控制之前实例化的东西。但是当包含来自我无法控制的另一个站点的 js 时,仍然存在问题。

想法?

编辑:

我现在接受了最好的答案,因为它很好地解释了正在发生的事情。但是,如果有人对如何改进这一点有任何建议,我对他们持开放态度。这是我想做的一个例子。

// Include() is a custom function to import js.
Include('my1.js');
Include('my2.js');

myFunc1('blarg');
myFunc2('bleet');

我只是不想过多地了解内部结构,而只想说:“我希望使用这个module,现在我将使用它的一些代码。”

6个回答

您可以<script>使用“onload”处理程序创建元素,该处理程序将在浏览器加载和评估脚本时调用。

var script = document.createElement('script');
script.onload = function() {
  alert("Script loaded and ready");
};
script.src = "http://whatever.com/the/script.js";
document.getElementsByTagName('head')[0].appendChild(script);

你不能同步进行。

编辑- 有人指出,正如形式一样,IE 不会<script>在加载/评估标签时触发“加载”事件因此,我想接下来要做的是使用 XMLHttpRequest 获取脚本,然后eval()自己获取。(或者,我想,将文本填充到<script>您添加标签中; 的执行环境eval()受本地范围的影响,因此它不一定会执行您希望它执行的操作。)

编辑-截至 2013 年初,我强烈建议您寻找更强大的脚本加载工具,如Requirejs有很多特殊情况需要担心。对于非常简单的情况,有yepnope,它现在内置在Modernizr 中

@Pointy 我通过使用 XMLHttpRequest 解决了这个问题,然后eval(). 但是,调试它是一场噩梦 b/c 错误消息报告该行eval()出现,而不是实际错误
2021-03-15 12:25:21
真的??加载脚本时谁不触发“加载”事件? 等等- 不要告诉我。
2021-03-17 12:25:21
当然, document.write() 就是你要找的。不漂亮,但它有效。
2021-03-17 12:25:21
但是requirejs是如何做到这一点的呢??他们如何包含许多脚本并以正确的顺序触发它们?
2021-04-03 12:25:21
不幸的是,它不是跨浏览器。
2021-04-10 12:25:21

这并不漂亮,但它有效:

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
</script>

<script type="text/javascript">
  functionFromOther();
</script>

或者

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
  window.onload = function() {
    functionFromOther();
  };
</script>

脚本必须包含在单独的<script>标记中或之前window.onload()

这将不起作用:

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
  functionFromOther(); // Error
</script>

创建节点也可以这样做,就像 Pointy 所做的那样,但仅限于 FF。您无法保证脚本何时可以在其他浏览器中就绪。

作为一个 XML 纯粹主义者,我真的很讨厌这个。但它确实可以预见。您可以轻松地包装那些丑陋的document.write()s,这样您就不必看它们了。您甚至可以进行测试并创建一个节点并将其附加到document.write().

您确定您的第一个代码片段适用于所有浏览器吗?
2021-03-22 12:25:21
@BogdanGusiev 我不是 100% 确定。我在 IE 8、(当时的当前版本)Firefox 和 Chrome 中进行了测试。这可能不适用于作为内容类型的 XHTML 文档类型application/xhtml+xml
2021-03-28 12:25:21
不幸的是,脚本标签不能在 JS 文件中使用。
2021-04-04 12:25:21
你不应该再使用 document.write() 了。请参阅:developers.google.com/web/updates/2016/08 /... & varvy.com/pagespeed/avoid-document-write.html
2021-04-04 12:25:21
@Clem 你可以做一个document.write("<SCR" + "IPT>" + "...").
2021-04-09 12:25:21

这已经很晚了,但为了将来供任何想要这样做的人参考,您可以使用以下内容:

function require(file,callback){
    var head=document.getElementsByTagName("head")[0];
    var script=document.createElement('script');
    script.src=file;
    script.type='text/javascript';
    //real browsers
    script.onload=callback;
    //Internet explorer
    script.onreadystatechange = function() {
        if (this.readyState == 'complete') {
            callback();
        }
    }
    head.appendChild(script);
}

前段时间我写了一篇关于它的简短博客文章http://crlog.info/2011/10/06/dynamically-requireinclude-a-javascript-file-into-a-page-and-be-notified-when-its -加载/

这真的有效吗?看我的问题:stackoverflow.com/questions/17978255/...
2021-04-02 12:25:21
这看起来很有趣。一个问题……为什么要执行两次回调方法?(script.onload=callback 和 callback() 在 onreadystatechange 中使用)
2021-04-07 12:25:21
onreadysteatechange 适用于 IE,只会在 IE 上触发,因为 onload 不会对 IE 触发
2021-04-10 12:25:21

上面的答案为我指明了正确的方向。这是我工作的通用版本:

  var script = document.createElement('script');
  script.src = 'http://' + location.hostname + '/module';
  script.addEventListener('load', postLoadFunction);
  document.head.appendChild(script);

  function postLoadFunction() {
     // add module dependent code here
  }      
什么时候postLoadFunction()叫?
2021-03-18 12:25:21
@JoshJohnsonscript.addEventListener('load', postLoadFunction);表示在脚本加载时调用 postLoadFunction。
2021-03-25 12:25:21

异步编程稍微复杂一些,因为发出请求的结果被封装在一个函数中,而不是跟随请求语句。但是用户体验的实时行为可能会明显更好,因为他们不会看到缓慢的服务器或缓慢的网络导致浏览器表现得好像它已经崩溃。同步编程是不尊重的不应在人们使用的应用程序中使用。

道格拉斯·克罗克福德 YUI 博客

好吧,扣好你的座位,因为这将是一段颠簸的旅程。越来越多的人询问通过javascript动态加载脚本,这似乎是一个热门话题。

它变得如此受欢迎的主要原因是:

  • 客户端module化
  • 更容易的依赖管理
  • 错误处理
  • 性能优势

关于module化:很明显,管理客户端依赖项应该在客户端正确处理。如果需要某个对象、module或库,我们只需请求它并动态加载它。

错误处理:如果资源失败,我们仍然有机会仅阻止依赖于受影响脚本的部分,或者甚至可能会延迟一段时间再试一次。

性能已经成为网站之间的竞争优势,它现在是一个搜索排名因素。动态脚本可以做的是模仿异步行为,而不是浏览器处理脚本的默认阻塞方式。脚本阻止其他资源,脚本阻止进一步解析 HTML 文档,脚本阻止UI。现在使用动态脚本标签及其跨浏览器替代方案,您可以执行真正的异步请求,并仅在它们可用时执行相关代码。即使使用其他资源,您的脚本也将并行加载,并且渲染将完美无缺。

有些人之所以坚持同步脚本,是因为他们已经习惯了。他们认为这是默认的方式,这是更简单的方式,有些人甚至认为这是唯一的方式。

但是,当需要就应用程序的设计做出决定时,我们唯一应该关心的是最终用户体验在这个领域异步是不可战胜的。用户立即得到响应(或说Promise),Promise总比没有好。一个空白的屏幕会吓到人们。开发人员不应该懒于提高感知性能

最后是关于肮脏的一面。为了让它在浏览器中工作,你应该做什么:

  1. 学会异步思考
  2. 将您的代码组织成module化
  3. 组织您的代码以很好地处理错误和边缘情况
  4. 逐步提高
  5. 始终注意适当数量的反馈
谢谢,加拉姆。我想我应该更清楚。我确实希望这最终是异步的。我只是想要一种对程序员来说合乎逻辑的访问它的方法。我想避免这样的事情: Import("package.mod1", function() { // do stuff with mod1 }); Import("package.mod2", function() { // 使用 mod2 }); 我查看了您的脚本和 labjs,虽然不错,但似乎更符合我的需求。我认为可能有一种更简单的方法,并希望避免引入额外的依赖项。
2021-03-11 12:25:21
你错过了我的帖子的重点。这一切都与用户有关。这应该是您的首要任务。其他一切都是次要的。
2021-03-14 12:25:21
再次,为了清楚起见,“同步”是用来表达我的观点的错误选择。我不希望浏览器在加载内容时冻结。
2021-03-15 12:25:21
加拉姆,很好的观点。用户体验非常重要。需要明确的是,我不愿意牺牲用户体验或质量、可维护的代码。我将研究closure和labjs,看看它们能为我做什么。但目前我可能需要坚持使用 <script> 标签。不幸的是,我不是一个人在做这件事。我与一个中等规模的开发人员团队合作,因此可维护的代码是重中之重。如果每个人都无法弄清楚如何有效地使用 lib,那么用户 exp 就直接退出窗口。回调是直观的。回调是因为你导入了一个包不是。
2021-03-22 12:25:21
如果需要同步加载怎么办?如果您确实需要阻止以保留用户体验。如果您使用基于 JavaScript 的 A/B 或 MVT 测试系统。您希望如何异步加载内容并替换默认值而不会出现破坏用户体验的闪烁效果?我愿意接受建议。我有 500 多名同事想知道解决方案。如果你没有,请不要用诸如“同步编程是不尊重的,不应在人们使用的应用程序中使用”之类的表达。
2021-03-30 12:25:21