【问题标题】:Dynamic script addition should be ordered?动态脚本添加应该命令吗?
【发布时间】:2010-05-10 16:02:47
【问题描述】:

我在页面加载后动态添加一些<script> 标签到head 元素。我知道脚本是异步加载的,但我可以期望它们按照添加的顺序进行解析吗?

我在 Firefox 中看到了预期的行为,但在 Safari 或 Chrome 中没有。查看 Chrome 开发人员工具和 Firebug 中的文档,均显示以下内容 -

<html>
  <head>
    ...
    <script type="text/javascript" src="A.js"></script>
    <script type="text/javascript" src="B.js"></script>
  </head>
  ...
</html>

但是查看资源加载视图,chrome 似乎会解析服务器首先返回的内容,而 firebug 总是按照添加脚本标签的顺序加载它们,即使 B 首先从服务器返回。

我应该期望 Chrome/Safari 以指定的顺序解析文件吗?在 OS X 10.6.3 上使用 Chrome 5.0.375.29 测试版

编辑(10/5/10):当我说解析时,我的意思是执行 - 可以看到积极解析的许多好处 - thx rikh

编辑(2010 年 11 月 5 日):好的,所以我按照下面 juandopazo 的思路进行了测试。但是我添加了一些东西,包括

  1. 直接使用javascript将脚本元素添加到头部。 (测试 A -> D)
  2. 使用 jquery 的 append() 方法将脚本元素添加到头部。 (测试 E -> H)
  3. 使用 jquery 的 getScript() 方法“加载”脚本。 (测试 I -> L)

我还尝试了脚本标签上“异步”和“延迟”属性的所有组合。

您可以在此处访问测试 - http://dyn-script-load.appspot.com/,并查看源代码以了解其工作原理。加载的脚本只需调用 update() 函数。

首先要注意的是,只有上面的第 1 和第 3 方法并行操作 - 第 2 方法按顺序执行请求。你可以在这里看到这个图表 -

图 1 - 请求生命周期图
Request lifecycle Graph http://dyn-script-load.appspot.com/images/dynScriptGraph.png

有趣的是,jquery append() 方法也阻塞了 getScript() 调用——你可以看到它们都不会执行,直到所有 append() 调用都完成,然后它们都并行运行。最后要注意的是,jQuery append() 方法一旦执行就会从文档头部删除脚本标签。只有第一种方法将脚本标签留在文档中。

Chrome 结果

结果是 Chrome 总是执行第一个返回的脚本,不管测试如何。这意味着所有测试都“失败”,除了 jQuery append() 方法。

图 2 - Chrome 5.0.375.29 测试版结果
Chrome Results http://dyn-script-load.appspot.com/images/chromeDynScript.png

火狐搜索结果

然而,在 firefox 上,如果使用第一种方法,并且 async 为 false(即未设置),那么脚本将可靠地按顺序执行。

图 3 - FF 3.6.3 结果
FF Results http://dyn-script-load.appspot.com/images/ffDynScript.png

请注意,Safari 似乎以与 Chrome 相同的方式提供不同的结果,这是有道理的。

另外,我在慢速脚本上只有 500 毫秒的延迟,只是为了减少开始->结束时间。您可能需要刷新几次才能看到 Chrome 和 Safari 都失败了。

在我看来,如果没有这样做的方法,我们就没有利用并行检索数据的能力,我们没有理由不应该这样做(如 firefox 所示)。

【问题讨论】:

标签: javascript dynamic-script-loading


【解决方案1】:

很抱歉回答我自己的问题,但已经有一段时间了,我们确实想出了一个解决方案。我们想出的是将 javascript 作为包含在 json 对象中的文本同时加载,然后在它们全部加载后使用 eval() 以正确的顺序执行它们。并发加载加有序执行。根据您的用例,您可能不需要 json。粗略地说,这里有一些代码显示了我们所做的 -

// 'requests' is an array of url's to javascript resources
var loadCounter = requests.length;
var results = {};

for(var i = 0; i < requests.length; i++) {
   $.getJSON(requests[i], function(result) {
      results[result.id] = result;
      ...
      if(--loadCounter == 0) finish();
   });
}

function finish() {
  // This is not ordered - modify the algorithm to reflect the order you want
  for(var resultId in results) eval(results[resultId].jsString);
}

【讨论】:

    【解决方案2】:

    据我了解,它们应该按照它们在文档中出现的顺序执行。某些浏览器可能能够执行一些乱序解析,但它们仍然必须以正确的顺序执行。

    【讨论】:

    • 这也是我的理解。
    【解决方案3】:

    不,您不能期望所有浏览器都会推迟执行这两个脚本,直到它们都被加载(**特别是当您动态添加它们时)。

    如果您只想在 A.js 加载后执行 B.js 中的代码,那么最好的办法是添加一个 onload 回调到 A.js 设置一个变量,另一个到 B.js 以检查查看该变量是否已设置,如果已设置,它会在 B.js 中执行必要的函数(如果 A.js 尚未加载,它会启动一个计时器,定期检查直到它已加载)。

    【讨论】:

      【解决方案4】:

      下载顺序和执行顺序不是一回事。在你的页面中,即使先下载了B.js,浏览器的引擎也会等待A.js继续处理页面。

      脚本肯定会被处理,不仅按照它们在文档中出现的顺序,而且在它们出现的位置。

      想象一下,如果不是这样,如果你的使用jQuery的小脚本在jQuery库之前被下载和处理,会出现很多错误。

      此外,当您在 js 文件中执行“document.write”时,它会出现在声明脚本的位置。您也无法访问出现在脚本声明之后的 DOM 对象。

      这就是为什么建议将脚本放在页面最底部的原因,以防止它们过早执行并减少页面的“感知加载时间”,因为浏览器的渲染引擎会在脚本一旦停止已处理。

      迈克

      编辑:如果它们是用 javascript 动态添加的,我认为它们是按照它们及时添加的顺序处理的。

      【讨论】:

      • 太棒了,这正是我所期待的 - 所以如果我看到脚本 not 以它们在文档中出现的顺序执行(这也是它们的顺序)是及时添加的),那么这将是 WebKit 中的错误吗?我有四件事告诉我发生了什么 (1) DOM 的实际结构以正确的顺序显示脚本 (2) 开发工具中的资源加载视图显示首先接收到的第二个脚本 (3) 控制台显示脚本添加顺序的日志(及时),以及 (4) 显示第二个脚本首先执行的控制台日志
      • 也许在A.js的最后一行,通过“document.write”放一行来包含B.js?
      • 主要目标是并行加载脚本,但按添加顺序执行。这适用于 Firefox,但不适用于 chrome/safari。我可以在测试中使用第二种方法(参见主帖中的编辑)来保证所有浏览器中的正确执行顺序,但它不是并发的。我在想,与其采用您建议的方法来获取串行加载(并因此执行),不如使用 jQuery.append(),因为脚本本身不需要额外的代码。也就是说,我现在有串行负载工作 - 我正在寻找一种并发负载解决方案来提高性能 - 干杯。
      【解决方案5】:

      您可以从 a.js 加载 b.js 以确保 100% 确定......尽管我自己也希望得到这个问题的明确答案,尤其是同步 ajax 加载脚本。

      【讨论】:

      • 这绝对有效 - 我正在放弃这样做,以获得同时加载脚本的性能优势:)
      【解决方案6】:

      我在开发一个像 YUI 3 一样动态加载模块的小库时对此进行了调查。我在这里创建了一个小测试,加载两个仅将内容插入 div 的脚本。一个是普通的JS文件,一个是等待3秒执行的PHP文件。

      http://www.juandopazo.com.ar/tests/asyn-script-test.html

      如您所见,脚本在完成加载后执行,而不是按照您将它们附加到 DOM 的顺序在每个浏览器中执行。

      【讨论】:

      • 我查看了测试的 DOM 结构,并注意到了几件事 - 首先脚本在正文而不是头部,第二个脚本首先列出 - 所以如果订购很荣幸,您会期望脚本 2 首先执行 - 好的测试虽然会很好地在脚本标签有序时看到结果。 ta
      • 对。对不起,我忘了把它们放在头上。它们的顺序错误,因为我使用了 insertBefore() 而不是 appendChild()。我改变了它,结果是一样的。您应该考虑编写某种队列。在不同文件中请求从全局对象调用方法的模块。看看 YUI3 是如何工作的。带有您代码的脚本通常如下所示: YUI().use('module1', 'module2', function (Y) { //do something });每个模块开始: YUI().add('module1', function (Y) { // 添加你的东西到 Y });
      • 谢谢你 - 我没有足够的声誉来标记抱歉。只是想弄清楚为什么我在我的场景中看到 Firefox 的行为符合预期,但在你的场景中却没有。看来我可能误读了我的情况。
      • 我正在查看您添加到脚本标记的“异步”属性。在这里阅读 - whatwg.org/specs/web-apps/current-work/#attr-script-async,似乎很明显它会影响脚本的执行时间,但是很难清楚地了解在动态添加脚本的情况下会发生什么。将做一些测试,看看这些属性会发生什么。
      猜你喜欢
      • 1970-01-01
      • 2012-11-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-19
      • 1970-01-01
      • 1970-01-01
      • 2014-04-06
      相关资源
      最近更新 更多