JavaScript引擎如何在浏览器中执行JavaScript?

8

问题不是为了解决,而是为了更好地了解系统

专家们!我知道每当您将JavaScript代码输入JavaScript引擎时,它将立即执行。但由于我没有看到引擎的源代码,因此我有以下几个问题:

假设我从远程服务器加载两个文件,分别为FILE_1.js和FILE_2.js。 FILE_2.js中的代码需要FILE_1.js中的一些代码。 因此,我已按以下方式包含文件:

<script type="text/javascript" src="FILE_1.js" ></script>
<script type="text/javascript" src="FILE_2.js" ></script>

希望我已经满足了Javascript引擎的要求。不幸的是,我在FILE_1.js中编写了5000KB的代码,而在FILE_2.js中只有5KB的代码。由于服务器是多线程的,因此在FILE_1.js完成之前,肯定会将FILE_2.js加载到我的浏览器中。
Javascript引擎如何处理这种情况?
如果将代码从FILE_2.js移动到内联脚本标签中,Javascript引擎需要采取哪些行动来管理这种依赖关系?
<script type="text/javascript" src="FILE_1.js" ></script>
<script type="text/javascript" >
// Dependent code goes here
</script>

注意:我不希望得到单个词的回答。我只是想深入了解是谁管理发出请求,是浏览器、JavaScript引擎还是普通人?如果请求/响应由普通人处理,那么JavaScript引擎如何知道这一点?

3
在执行文件2之前,服务器将等待文件1完全加载,因为文件2可能依赖于它。如果它们是独立的,可以使用模块加载器,或者将“defer”属性添加到脚本中。 - Benjamin Gruenbaum
@BenjaminGruenbaum 是正确的,只要您按照那个顺序加载它们。话虽如此,我希望 5000KB 是编造的数字;对于客户端脚本来说,那太大了! - elixenide
1个回答

12

当我发布关于代码行为的答案时,我总是喜欢去两个地方:

  1. 规范
  2. 实现

规范:

DOM API明确指定脚本必须按顺序执行:

如果元素具有src属性,没有async属性,并且没有设置“force-async”标志,则该元素必须添加到与脚本元素的文档在准备脚本算法启动时相关联的尽快按顺序执行的脚本列表的末尾

来自4.1 Scripting。请在使用deferasync属性之前检查此规则的例外列表。这在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.

通常最好使用模块加载器(它将延迟脚本插入和执行,并为您正确管理依赖项)。

目前,最好的方法是使用类似于BrowserifyRequireJS的东西。在未来,我们将能够使用ECMAScript 6模块。

实现:

好吧,你提到了它,我无法抵挡。因此,如果我们检查Chromium blink源代码(在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();
    }

自从ECMAScript 6发布以来,您能否更新这个答案?谢谢。 - Mark
为什么这很重要?它是与DOM API不同的规范。 - Benjamin Gruenbaum
由于你指出最好使用模块加载器,并且你说ES6可以用来实现它,我想更多了解这个。 - Mark
1
@Mark 目前ECMAScript 6模块还不够成熟(遗憾),虽然它们可以工作,但大多数人仍然使用webpack(这是正确的选择)。 - Benjamin Gruenbaum
@BenjaminGruenbaum 为什么ES6模块还没有准备好用于生产环境?所有主流浏览器都支持它,除了IE。如果你不在意IE,我认为使用ES6模块是安全的。 - Yuriy Kravets

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