动态脚本添加应该有顺序吗?

7
我在页面加载后动态向头部元素添加一些<script>标签。我知道这些脚本是异步加载的,但我能否期望它们按照添加的顺序被解析?
我在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 beta。 编辑(10/5/10):当我说解析时,我的意思是执行-可以看到积极解析的许多好处-thx rikh 编辑(11/5/10):好的,所以我按照juandopazo以下的方法进行了测试。但是我添加了一些组合,包括:1.使用JavaScript将脚本元素直接添加到头部。(测试A->D)2.使用jquery的append()方法将脚本元素添加到头部。(测试E->H)3.使用jquery的getScript()方法“加载”脚本。(测试I->L)我还尝试了所有脚本标记上的“async”和“defer”属性的组合。
您可以在此处访问测试 - 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 beta 结果
Chrome结果 http://dyn-script-load.appspot.com/images/chromeDynScript.png

Firefox结果

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

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

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

此外,我只在缓慢的脚本上延迟了500毫秒,只是为了将开始-完成时间降低。您可能需要刷新几次才能看到Chrome和Safari在所有方面都失败。

在我看来,如果没有一种方法来做到这一点,我们就无法利用并行检索数据的能力,而没有理由不这样做(如Firefox所示)。


我已经在Chrome问题注册表中报告了这个错误 - http://code.google.com/p/chromium/issues/detail?id=46109 - hawkett
6个回答

3

很抱歉自己回答问题,但已经有一段时间了,我们找到了解决方案。我们的解决方案是将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);
}

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

这也是我的理解。 - hawkett

0

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

如果你想在B.js中只有在A.js被加载后才执行代码,那么最好的方法是在A.js中添加一个onload回调函数来设置一个变量,并在B.js中添加另一个检查该变量是否已经设置的回调函数,然后如果已经设置了,就在B.js中执行必要的函数(如果A.js还没有加载,则启动一个定时器定期检查直到它被加载为止)。


0

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

脚本肯定是按照它们出现在文档中的顺序进行处理的,而且还要按照它们出现的位置进行处理。

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

此外,当您在 js 文件中使用 "document.write" 时,它将出现在声明脚本的位置。您也无法访问在脚本声明之后出现的 DOM 对象。

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

Mike

编辑:如果它们是通过 JavaScript 动态添加的,则我认为它们按照它们添加的时间顺序进行处理。


太好了,这正是我期望的 - 所以如果我看到脚本按照它们在文档中出现的顺序执行(这也是它们添加的顺序),那么这将是 WebKit 中的一个错误?有四件事情告诉我发生了什么:(1)DOM 的实际结构显示脚本按正确的顺序排列(2)开发工具中的资源加载视图显示第二个脚本先被接收(3)控制台日志显示脚本添加的顺序(按时间),以及(4)控制台日志显示第二个脚本先被执行。 - hawkett
也许在A.js的最后一行,通过"document.write"添加一行来包含B.js? - Mike Gleason jr Couturier
主要目标是并行加载脚本,但按添加顺序执行。这在Firefox中有效,但在Chrome/Safari中无效。我可以使用测试中的第二种方法(请参见主帖中的编辑)来保证所有浏览器中正确的执行顺序,但它不是并发的。我认为与其采用您建议的串行加载方法(从而实现执行),我会选择jQuery.append(),因为脚本本身不需要额外的代码。话虽如此,我现在已经实现了串行加载 - 我正在寻找一种并行加载解决方案以提高性能 - 干杯。 - hawkett

0

你可以从 a.js 中加载 b.js,以确保100%的可靠性……虽然我自己也很想对这个问题得出确定的答案,特别是在同步ajax加载脚本的情况下。


那绝对可行 - 我正在逐步放弃这种做法,以获得同时加载脚本的性能优势 :) - hawkett

0
我正在研究一个类似 YUI 3 动态加载模块的小型库时发现了这个问题。我在这里创建了一个小测试,它加载了两个脚本,这些脚本仅将内容插入到 div 中。其中一个是常规 JS 文件,另一个是等待 3 秒才执行的 PHP 文件。

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

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


我查看了测试的DOM结构,并注意到了一些事情 - 首先,脚本位于body而不是head中,第二个脚本排在第一位 - 因此,如果按顺序执行,您会期望脚本2首先执行 - 这是一个很好的测试,但当脚本标签有序时,看到结果会更好。 - hawkett
好的。抱歉,我忘记把它们放在头部了。它们的顺序错了,因为我使用了insertBefore()而不是appendChild()。我改变了它,结果是一样的。你应该考虑编写某种队列。要求在不同文件中调用全局对象的方法。看看YUI3的工作方式。包含您代码的脚本通常如下所示:YUI().use('module1', 'module2', function (Y) { //do something });每个模块都以以下方式开始:YUI().add('module1', function (Y) { // add your stuff to Y }); - juandopazo
谢谢你,我没有足够的声望来标记抱歉。只是试图弄清楚为什么在我的情况下看到Firefox表现正常,但在你的情况下不是。看起来我可能误读了我的情况。 - hawkett
我正在研究您在脚本标记中添加的'asynch' 属性。根据这里的说明 - http://www.whatwg.org/specs/web-apps/current-work/#attr-script-async,它似乎清楚地说明了它将影响脚本何时执行,但很难得出一个清晰的图片,在动态添加脚本的情况下该怎么做。 我将进行一些测试,以查看这些属性会发生什么。 - hawkett

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