【问题标题】:Script File Executing After Inline Script With CDN or External Domain On HTML injection在 HTML 注入中使用 CDN 或外部域的内联脚本后执行的脚本文件
【发布时间】:2016-01-24 14:12:16
【问题描述】:

我在将 HTML 注入到已加载的 DOM 中时遇到问题,在该 DOM 中,在下载脚本文件后正在加载内联 javascript。据我所知,这不应该是异步的,内联脚本应该在脚本文件之后执行。如果域名与调用页面相同,则此方法有效,但使用 CDN 甚至子域会做同样的事情。我应该做些什么来修改我如何称呼这些?我发誓这在我使用 CDN 一个多星期之前有效,但也许我从未发现过这个问题。

控制台

Loading Inline Script
VM1400:3 Uncaught TypeError: Cannot read property 'init' of undefined(anonymous function) 
app.members.event.js?v=204&_=1453644424985:5 Loading Script File
app.members.event.js?v=204&_=1453644424985:71 Finished Script File

Javascript

<script type="text/javascript" src="https://test.azureedge.net/Areas/Directors/scripts/app.members.event.js?v=204"></script>
<script type="text/javascript">
console.log('Loading Inline Script');
    app.viewModel.members.event.init();
console.log('Finished Inline Script');

【问题讨论】:

  • 我认为您的 JS 代码有问题/缺失。你有 });没有左括号。
  • 我只是把它剪掉了,代码没有问题,因为它发生在其他页面上。
  • 显然我在你的观点中遗漏了一些东西,一旦这个问题被改变,我的反对票就会被删除,只是@-me。我的猜测是您的代码错误或您的脚本网址已损坏。
  • 你还没有说明你为什么投反对票。你说你会删除它,现在你不是。这只是您给出错误答案的骄傲。
  • TL;DR 这篇文章可能会说更多内容给你一个提示:stackoverflow.com/questions/8996852/…

标签: javascript dom asynchronous cdn deferred-execution


【解决方案1】:

一种方法是使用jquery's getScript() 函数。

但最好是使用原生 javascript 加载脚本文件,然后运行内联脚本。

也许我没有清楚地理解这个问题。

编辑: 这是HTML5 规范中关于脚本元素的引用。

如果元素具有 src 内容属性,请运行以下子步骤:

设 src 为元素的 src 属性的值。

如果 src 是空字符串,则将任务排队以触发名为的简单事件 元素出错,并中止这些步骤。

相对于元素解析 src。

如果上一步失败,将任务排队以触发名为 元素出错,并中止这些步骤。

对生成的绝对 URL 进行可能启用 CORS 的提取, 模式是元素跨域的当前状态 内容属性,原点是脚本元素的原点 文档,并将默认来源行为设置为 taint。

以这种方式获得的资源可以是CORS-same-origin 或 CORS 跨域。这只会影响错误报告的发生方式。

出于性能原因,用户代理可能会开始获取脚本(如 上面定义)只要设置了 src 属性,而是在 希望该元素将被插入到文档中(并且 crossorigin 属性同时不会改变值)。无论哪种方式, 一旦元素被插入到文档中,负载必须有 按照此步骤中的说明开始。如果 UA 执行此类 预取,但该元素从未插入到文档中,或者 src 属性是动态改变的,或者 crossorigin 属性是 动态更改,则用户代理不会执行脚本 如此获得,并且提取过程将有效 浪费了。

然后,描述情况的以下选项中的第一个 必须遵守:

如果元素有src属性,并且元素有defer 属性,并且该元素已被标记为“解析器插入”,并且 该元素没有异步属性 必须添加该元素 到文档时将执行的脚本列表的末尾 已完成与解析器的 Document 关联的解析 创建了元素。

联网任务源在任务队列中放置一次的任务 获取算法已经完成必须设置元素的“准备好 被解析器执行”标志。解析器将处理执行脚本。

如果元素具有 src 属性,并且该元素已被标记 作为“解析器插入”,并且该元素没有异步属性 该元素是 Document 的待处理解析阻止脚本 创建元素的解析器。 (只能有这样一种 一次为每个文档编写脚本。)

联网任务源在任务队列中放置一次的任务 获取算法已经完成必须设置元素的“准备好 被解析器执行”标志。解析器将处理执行脚本。

如果元素没有src属性,并且该元素已经 标记为“已插入解析器”,以及创建 script 是一个 XML 解析器,或者它是一个 HTML 解析器,其脚本嵌套 level 不大于 1,并且 HTML 解析器的 Document 或 创建脚本元素的 XML 解析器有一个样式表,它是 阻塞脚本 元素是待处理的解析阻塞脚本 创建元素的解析器的文档。 (只能有 一次是每个文档一个这样的脚本。)

设置元素的“准备被解析器执行”标志。解析器将 处理执行脚本。

如果元素有 src 属性,没有 async 属性, 并且没有设置“force-async”标志必须添加元素 到将尽快按顺序执行的脚本列表的末尾 尽可能与脚本元素的 Document 相关联 准备脚本算法开始的时间。

联网任务源在任务队列中放置一次的任务 抓取算法已完成必须运行以下步骤:

如果该元素现在不是脚本列表中的第一个元素 它将尽快按添加到的顺序执行 上面,然后将元素标记为就绪,但中止这些步骤而不 正在执行脚本。

执行:执行第一个脚本对应的脚本块 此脚本列表中的元素,将立即按顺序执行 可能。

从将要执行的脚本列表中删除第一个元素 尽快安排。

如果这个脚本列表会尽快按顺序执行 仍然不是空的并且第一个条目已经被标记为 准备好,然后跳回到标有执行的步骤。

如果元素具有 src 属性,则该元素必须添加到 将尽快执行文档的一组脚本 准备脚本算法时的脚本元素 开始了。

联网任务源在任务队列中放置一次的任务 获取算法已完成必须执行脚本块和 然后从将执行为的脚本集中删除该元素 尽快。

否则用户代理必须立即执行脚本块, 即使其他脚本已经在执行。获取外部 脚本必须延迟元素文档的加载事件,直到 网络任务源排队的任务一旦资源 已获取(如上定义)已运行。

据此,我认为您的“外部”文件是在内联脚本块之后加载的。因此,我会使用 jquery 中的“getScript()”函数来确保脚本在内联脚本块之前加载。

【讨论】:

  • 不,我相信你在正确的轨道上。但是,为什么它在当前域上工作得很好,但如果我引用它就不行......
  • 我认为这与响应时间有关。当文件正常工作时,文件是否位于同一位置?缓存也可能与它有关。我看到您引用的是 ?v=204,您要更改这个数字吗?
  • 如果他们有相同的域,是的,他们在同一个服务器上。如果我更新到子域,它们仍然在同一台服务器上。如果我更新到 CDN,当然它是不同的服务器。除非我明确告诉它,否则版本不会改变。我同意它的响应时间,但脚本不应该在内联脚本之前先执行。
  • 是的,脚本应该在正确的内联脚本之前执行。也许它与客户端缓存问题有关。尝试清空浏览器缓存并再次从外部服务器加载脚本。
  • 我相信这是答案,因为它适用于同一个域,但不适用于另一个域。我有许多应该按顺序加载的脚本,使用 CDN 似乎是不可能的。我可能需要为脚本文件坚持同一个域。
【解决方案2】:

这是注射场景中的常见问题。它是由于脚本可用性的可变延迟以及各种浏览器上的并行和不同实现而发生的。

有 3 个选项,取决于源代码是否可编辑,以及脚本文件之间是否存在 2 个以上的依赖关系。

选项1.在脚本标签中使用defer属性

如果两个脚本都是远程的(即非内联),则可以使用此选项

"defer" 向浏览器指示脚本必须在文档被解析后执行(引用自 MDN)。这仅适用于具有“src”属性的远程(非内联)脚本。

https://html.spec.whatwg.org/multipage/scripting.html#attr-script-defer

你可以像下面这样使用它。

主要浏览器都支持延迟,我在 Chrome、Firefox、Tizen 上的 Webkit 和 Safari 上进行了验证:

https://developer.mozilla.org/en/docs/Web/HTML/Element/script#Browser_compatibility

要提供上述情况的具体示例,请参阅下文。请注意,以下内容已在 Firefox、Chrome、IE11、iPhone 上的 Safari 和 Tizen 上的 Webkit 上得到验证。

案例一:

许多 Javascript 文件 - 全部独立:

如果没有依赖,“defer”属性允许快速加载 HTML。脚本在下载后接管,没有问题(假设 onload 等被注意)。

案例 2:

两个 javascript 文件 test1.js 和 test2.js - 一个依赖于另一个:

如果 test2.js 依赖于 test1.js 的加载,那么 test2.js 的 script 标签“only”应该有 defer 属性。

这种用法显示在

http://www.gpupowered.org/loadtest/2_defer.html

不正确的用法显示在

http://www.gpupowered.org/loadtest/no_defer.html(两个脚本都没有 defer 标签 - 失败) http://www.gpupowered.org/loadtest/all_defer.html(两个脚本都有 defer 标签 - 这也失败了)

不工作的异步使用在,

http://gpupowered.org/loadtest/2_async.html(失败)

“推迟”在哪里不满足需求?

如果功能被拆分为多个 JS 文件(比如 n),并且需要在第 n 个文件开始处理某些变量之前下载所有“n-1”,即使可能存在“defer”属性在所有脚本源标签上,它被渲染为无关紧要,因为它们被接收的顺序是不确定的。

有关延迟的更多背景信息和延迟加载的各种选项(不包括多延迟情况) http://www.html5rocks.com/en/tutorials/speed/script-loading/

https://developer.mozilla.org/en/docs/Web/HTML/Element/script

选项 2:使用状态变量

如果可以将一些额外的状态变量添加到两个 javascript 源文件中,则可以使用此选项。

该方法依赖于依赖 js 文件中的命名变量,以及使用依赖文件的 js 文件中的命名函数。如果在用户文件尝试访问其功能时未加载依赖文件,则它会退出并在真正加载时被回调。

这在下面的 html 文件中进行了演示。

http://gpupowered.org/loadtest/variable.html(正常工作)

如果需要重复加载(即加载多个同名文件等),则此选项不起作用。

选项 3:本机脚本加载器

在这种情况下,有多个相互依赖的javascript文件。

对于这种情况,没有使用 defer 或 async 或其他规范提供的标签的解决方案。对于我在 gpupowered.org 的远程实验室中的用例,我必须使用 XMLHttpRequest 实现我自己的本机脚本加载器,下面的链接中提供了源代码。这使用了工作线程,因为我拥有的一些纹理相当大。回调函数可用于根据应用程序的需要实现依赖逻辑。例如,统计所有加载的脚本,然后触发完整执行等。

https://github.com/prabindh/gpupowered.gl/blob/master/worker/worker_object_loader.js

jquery 脚本加载器也使用 HTTP 请求,但我没有检查它是否使用工作器进行加载。 https://api.jquery.com/jquery.getscript/

【讨论】:

    【解决方案3】:

    我有两个理论:

    1. 可能是外部脚本中的某些内容延迟了 app.viewModel.members 对象的创建(超时或需要一段时间才能完成的事件处理程序)。这可以通过在您的内联脚本中设置较长的超时时间(f.i. 5000+ms)然后检查模型对象是否存在来轻松测试。

    2. 加载同源脚本时会发生一些奇怪的事情。

    在这种情况下,您可以尝试通过执行以下操作来延迟内联脚本的执行:

    <script type="text/javascript">
    document.addEventListener("DOMContentLoaded", function(event) {
        app.viewModel.members.event.init();
    });
    </script>
    

    或者只是将您的内联代码放在一个外部 .js 文件中并使用“延迟”标志调用它:

    <script type="text/javascript" src="https://test.azureedge.net/Areas/Directors/scripts/app.members.event.js?v=204"></script>
    <script type="text/javascript" src="{link-to-external-js-file}" defer></script>
    

    【讨论】:

      【解决方案4】:
      function onloadCallback(){
          app.viewModel.members.event.init();
      }
      

      【讨论】:

        【解决方案5】:

        使用这个:

        <script type="text/javascript" src="https://test.azureedge.net/Areas/Directors/scripts/app.members.event.js?v=204&onload=onloadCallback"></script
        
        <script type="text/javascript">function onloadCallback(){
        app.viewModel.members.event.init();}</script>
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-06-21
          • 1970-01-01
          • 2016-04-11
          • 1970-01-01
          • 1970-01-01
          • 2017-06-14
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多