当我发布关于代码行为的答案时,我总是喜欢去两个地方:
- 规范
- 实施
规格:
DOM API 明确指定脚本必须按顺序执行:
如果元素具有 src 属性,没有 async 属性,并且没有设置“force-async”标志
,则必须将元素添加到将按顺序执行的脚本列表的末尾尽快关联在准备脚本算法开始时使用脚本元素的文档。
从4.1 脚本。defer
请在使用orasync
属性之前检查此规则的例外列表。这在4.12.1.15中有很好的说明。
这是有道理的,想象一下:
//FILE_1.js
var trololo = "Unicorn";
....
// 1 million lines later
trololo = "unicorn";
var message = "Hello World";
//FILE_2.js
alert(message); // if file 1 doesn't execute first, this throws a reference error.
通常最好使用模块加载器(它将延迟脚本插入和执行,并为您正确管理依赖项)。
目前,最好的方法是使用Browserify或RequireJS之类的东西。将来,我们将能够使用 ECMAScript 6 模块。
实施:
好吧,你提到了它,我无法抗拒。因此,如果我们检查Chromium 闪烁源(在 WebKit 中仍然类似):
bool ScriptLoader::prepareScript(const TextPosition& scriptStartPosition,
LegacyTypeSupport supportLegacyTypes)
{
.....
} else if (client->hasSourceAttribute() && // has src attribute
!client->asyncAttributeValue() &&// and no `async` or `defer`
!m_forceAsync // and it was not otherwise forced
) { // - woah, this is just like the spec
m_willExecuteInOrder = true; // tell it to execute in order
contextDocument->scriptRunner()->queueScriptForExecution(this,
m_resource,
ScriptRunner::IN_ORDER_EXECUTION);
太好了,所以我们可以在源代码中看到它按解析顺序添加它们 - 就像规范所说的那样。
让我们看看脚本运行器是如何做的:
void ScriptRunner::queueScriptForExecution(ScriptLoader* scriptLoader,
ResourcePtr<ScriptResource> resource,
ExecutionType executionType){
.....
// Adds it in the order of execution, as we can see, this just
// adds it to a queue
case IN_ORDER_EXECUTION:
m_scriptsToExecuteInOrder.append(PendingScript(element, resource.get()));
break;
}
并且,使用计时器,它会在准备好时(或立即,如果没有待处理的内容)一个接一个地触发它们:
void ScriptRunner::timerFired(Timer<ScriptRunner>* timer)
{
...
scripts.swap(m_scriptsToExecuteSoon);
for (size_t i = 0; i < size; ++i) {
....
//fire!
toScriptLoaderIfPossible(element.get())->execute(resource);
m_document->decrementLoadEventDelayCount();
}