加载并执行脚本的顺序
在html页面中包含JavaScript的方式有很多种。 我知道以下选项:
defer
或async
属性(只有外部脚本) 从硬盘,javascript:URI和onEvent
-attributes [3]中不计算browserscripts,已经有16个替代品来执行JS,我确信我忘了一些东西。
我并不担心快速(并行)加载,我对执行顺序更加好奇(可能取决于加载顺序和文档顺序)。 是否有一个很好的 (跨浏览器) 参考涵盖了所有的情况? 例如http://www.websiteoptimization.com/speed/tweak/defer/只处理其中的6个,并且大多测试旧浏览器。
由于我担心没有,我的具体问题是:我有一些(外部)头文件用于初始化和脚本加载。 然后我在本体的最后有两个静态的内联脚本。 第一个让脚本加载器动态地将另一个脚本元素(引用外部js)附加到主体。 第二个静态内联脚本想要使用添加的外部脚本中的js。 它可以依靠另一个被执行(以及为什么:-)?
如果您不是动态加载脚本或将它们标记为延迟或异步,则会按页面中遇到的顺序加载脚本。 无论它是外部脚本还是内联脚本都无关紧要 - 它们按页面中遇到的顺序执行。 在外部脚本之后的内联脚本一直保留,直到所有外部脚本加载并运行。
异步脚本(不管它们如何被指定为异步)加载并以不可预知的顺序运行。 浏览器并行加载它们,并且可以按照它想要的顺序自由运行它们。
在多个异步事件中没有可预测的顺序。 如果需要一个可预测的顺序,那么它将不得不通过注册来自异步脚本的加载通知进行编码,并在加载适当的东西时手动排序JavaScript调用。
当动态插入脚本标记时,执行顺序的行为将取决于浏览器。 你可以看到Firefox在这篇参考文章中的表现。 简而言之,较新版本的Firefox会默认动态添加脚本标记以进行异步操作,除非脚本标记已被设置。
带有async
的脚本标记可以在加载后立即运行。 事实上,浏览器可能会暂停解析器,并执行该脚本。 所以,它几乎可以在任何时候运行。 如果脚本被缓存了,它可能立即运行。 如果脚本需要一段时间才能加载,则可能在解析器完成后运行。 用async
记住的一件事是,它可以随时运行,而且这个时间是不可预测的。
带有defer
的脚本标记等待整个解析器完成,然后defer
遇到的顺序运行标记为defer
所有脚本。 这使您可以将几个相互依赖的脚本标记为defer
。 它们都会被推迟到文档解析器完成之后,但它们将按照遇到它们的依赖关系的顺序执行。 我认为defer
就像脚本被放入队列一样,在解析器完成后将被处理。 从技术上讲,浏览器可能会随时在后台下载脚本,但直到解析器完成解析页面并解析并运行任何未标记为延迟或异步的内联脚本后,它们才会执行或阻止解析器。
这篇文章的引用如下:
脚本插入的脚本在IE和WebKit中异步执行,但在Opera和4.0之前的Firefox中同步执行。
HTML5规范的相关部分(适用于更新的兼容浏览器)就在这里。 那里写了很多关于异步行为的内容。 显然,这个规范并不适用于那些您可能需要测试才能确定的行为的旧浏览器(或不确定的浏览器)。
来自HTML5规范的引用:
然后,必须遵循以下描述情况的第一个选项:
如果元素具有src属性,并且元素具有defer属性,并且该元素已被标记为“解析器插入的”,并且该元素没有async属性该元素必须添加到列表的末尾当文档完成与创建该元素的解析器的Document相关联的解析时将执行的脚本。
一旦提取算法完成,网络任务源放置在任务队列中的任务必须设置元素的“准备好解析器执行”标志。 解析器将处理执行脚本。
如果元素具有src属性,并且该元素已被标记为“解析器插入的”,并且该元素没有async属性该元素是创建元素的解析器的Document的暂挂解析阻止脚本。 (每次文档只能有一个这样的脚本。)
一旦提取算法完成,网络任务源放置在任务队列中的任务必须设置元素的“准备好解析器执行”标志。 解析器将处理执行脚本。
如果元素没有src属性,并且该元素已被标记为“解析器插入”,并且创建脚本元素的HTML解析器或XML解析器的Document具有阻止脚本的样式表元素是等待创建该元素的解析器的Document的解析阻塞脚本。 (每次文档只能有一个这样的脚本。)
设置元素的“准备好解析器执行”标志。 解析器将处理执行脚本。
如果元素具有src属性,不具有async属性,并且没有设置“force-async”标志该元素必须添加到脚本列表的末尾,该脚本将尽快按顺序执行在准备脚本算法开始时与脚本元素的Document一起使用。
一旦获取算法完成,网络任务源放置在任务队列上的任务必须执行以下步骤:
如果该元素现在不是脚本列表中的第一个元素,它将按照上面所添加的顺序尽快执行,则将该元素标记为就绪,但不执行脚本而中止这些步骤。
执行:在该脚本列表中执行与第一个脚本元素相对应的脚本块,这些脚本将尽快按顺序执行。
从这个将尽快执行的脚本列表中删除第一个元素。
如果尽快按顺序执行的脚本列表仍然不是空的,并且第一个条目已经标记为准备就绪,那么跳回到标记为执行的步骤。
如果该元素具有src属性该元素必须添加到脚本元素的脚本集中,脚本元素的脚本文档在脚本算法启动时尽快执行。
一旦获取算法完成,网络任务源放置在任务队列上的任务必须执行脚本块,然后从尽快执行的脚本集中删除该元素。
否则 ,即使其他脚本已经在执行,用户代理也必须立即执行脚本块。
浏览器将按照发现它们的顺序执行脚本。 如果您调用外部脚本,它将阻止页面,直到脚本被加载并执行。
为了测试这个事实:
// file: test.php
sleep(10);
die("alert('Done!');");
// HTML file:
<script type="text/javascript" src="test.php"></script>
动态添加的脚本在追加到文档后立即执行。
为了测试这个事实:
<!DOCTYPE HTML>
<html>
<head>
<title>Test</title>
</head>
<body>
<script type="text/javascript">
var s = document.createElement('script');
s.type = "text/javascript";
s.src = "link.js"; // file contains alert("hello!");
document.body.appendChild(s);
alert("appended");
</script>
<script type="text/javascript">
alert("final");
</script>
</body>
</html>
警报顺序是“附加” - >“你好!” - >“最终”
如果在一个脚本中你试图访问一个尚未到达的元素(例如: <script>do something with #blah</script><div id="blah"></div>
),那么你将得到一个错误。
总的来说,是的,你可以包含外部脚本,然后访问它们的函数和变量,但只有当你退出当前的<script>
标签并开始一个新的<script>
。