24

我刚刚了解了一个关于在抛出错误时执行 Javascript 的重要事实。在我开始对此做出结论之前,我最好验证一下我是否正确。

给定一个包含 2 个脚本的 HTML 页面:

<script src="script1.js" />
<script src="script2.js" />

脚本1:

doSomething();

脚本2:

doSomeOtherThing();

这有效地导致单个脚本作为一个单元处理:

doSomething();
doSomeOtherThing();

特别是,如果doSomething抛出错误,则执行会中断。'script2' 永远不会被执行。

这是我的“第 1 课” ——有人可能会认为,因为它是一个单独包含的文件,它不受 script1 的影响。但它是。 => 请参阅下面的“延迟更新”


现在,如果我们改变 script2 如下(假设我们在上面的某个地方包含了 jQuery):

$(document).ready({ doSomeOtherThing(); });

并将脚本放在 script2 之前:

<script src="script2.js" />
<script src="script1.js" />

执行顺序实际上仍然是“doSomething()”,然后(有时)是“doSomeOtherThing()”。

然而,它以两个“单元”执行:

  • doSomething作为文档的 java 脚本的一部分提前执行
  • doSomeOtherThing在处理 document.ready 事件时执行。

如果doSomeOtherThing抛出异常,它不会破坏第二个处理“单元”。

(我避免使用该术语thread,因为我认为所有脚本通常由同一个线程执行,或者更准确地说,这可能取决于浏览器。)

所以,我的第 2 课:即使 JavaScript 错误可能会阻止任何后续脚本执行,它也不会停止事件循环。


结论 1

$(document).ready()在定义应该独立于任何其他脚本成功执行的 JavaScript 代码块方面做得很好。

或者,换句话说:如果你有一段 JavaScript 并且想要确保即使其他脚本失败也能执行它,将它放在一个$(document).ready().

这对我来说是新的,因为如果脚本依赖于完全加载的文档,我只会使用该事件。


结论 2

更进一步,将所有脚本包装在 a中可能是一个很好的架构决策,$(document).ready()以确保所有脚本都“排队”执行。在上面的第二个示例中,ifscript2.js包含 script1.js示例 1 之后:

<script src="script1.js" />
<script src="script2.js" />

script1.js 中的错误doSomeOtherThing()甚至会阻止注册,因为该$(document).ready()函数不会被执行。

但是,如果 script1.js 也使用$(document).ready()了 ,则不会发生这种情况:

$(document).ready(function() { doSomething(); });
$(document).ready(function() { doSomeOtherThing(); });

这两行都将被执行。然后稍后将执行事件循环,该循环会doSomething中断,但doSomeOtherThing不会受到影响。

这样做的另一个原因是渲染页面的线程可以尽快返回,并且可以使用事件循环来触发代码执行。


批评/问题:

  • 我弄错了吗?
  • 有什么原因需要立即执行一段代码即不将其包装到事件中?
  • 它会显着影响性能吗?
  • 是否有另一种/更好的方法来实现相同而不是使用文档就绪事件?
  • 如果所有脚本都只是将其代码注册为事件处理程序,是否可以定义脚本的执行顺序?事件处理程序是否按照它们注册的顺序执行?

期待任何有用的评论!

延迟更新:

就像 Briguy37 正确指出的那样,我的观察首先肯定是错误的。(“我是不是弄错了——是的!”)。以他的简单示例为例,我可以在所有主流浏览器甚至 IE8 中重现,即使 script1 抛出错误,也会执行 script2。

仍然@Marcello 的出色答案有助于深入了解执行堆栈等的概念。似乎这两个脚本中的每一个都在单独的执行堆栈中执行。

4

3 回答 3

16

JS 处理错误的方式取决于 JS 处理脚本的方式。它与线程无关(或很少)。因此,您必须首先考虑 JS 如何通过您的代码工作。

首先,JS 将依次读取每个脚本文件/块(此时您的代码仅被视为文本)。

比 JS 开始解释文本块并将它们编译成可执行代码。如果发现语法错误,JS 将停止编译并继续执行下一个脚本。在这个过程中,JS 将每个脚本块/文件作为单独的实体处理,这就是为什么脚本 1 中的语法错误不一定会中断脚本 2 的执行。代码将被解释和编译,但此时不会执行,所以一个throw new Error命令不会破坏执行。

编译完所有脚本文件/块后,JS 会遍历代码(从代码中的第一个代码文件/块开始)并构建所谓的执行堆栈(函数 a 调用函数 b 调用函数 d 和 c.... ) 并按给定的顺序执行它。如果在任何时候发生或以编程方式抛出处理错误(throw new Error('fail')),则该堆栈的整个执行将停止,并且 JS 将返回到该堆栈的开头并开始执行下一个可能的堆栈。

也就是说,你的onload函数在script1.js出错后仍然执行的原因,不是因为新线程什么的,只是因为一个事件建立了一个单独的执行堆栈,JS可以跳转到,之后上一个执行堆栈发生错误。

来回答你的问题:

有什么原因需要立即执行一段代码,即不将其包装到事件中?

我建议您在您的网络应用程序中根本没有“立即”调用的代码。最佳实践是在应用程序中有一个在 onload 事件内部调用的单一入口点

$(document).ready(function () {App.init()});

然而,这与错误处理等无关。错误处理本身绝对应该在您的代码中使用条件if(typeof myValue !== 'undefined')或 try/catch/finally 块来完成,您可能会想到潜在的错误。这也使您有机会在 catch 块内尝试第二种方式或在 finally 中优雅地处理错误。

如果您可以构建您的应用程序事件驱动(当然不是出于错误处理的原因),请这样做。JS 是一种事件驱动语言,在编写事件驱动代码时,您可以充分利用它...

它会显着影响性能吗?

恕我直言,事件驱动的方法将使您的应用程序性能更好,同时使其更加可靠。事件驱动代码可以帮助你减少内部处理逻辑的数量,你只需要进入它。

是否有另一种/更好的方法来实现相同而不是使用文档就绪事件?

如前所述:try/catch/finally

如果所有脚本都只是将其代码注册为事件处理程序,是否可以定义脚本的执行顺序?事件处理程序是否按照它们注册的顺序执行?

如果您在同一个对象上注册相同的事件,则会保留顺序。

于 2012-10-25T16:02:34.560 回答
9

您认为它们作为一个脚本运行的第一个假设是不正确的。即使 Script1 抛出错误,Script2 仍将执行。对于一个简单的测试,实现以下文件结构:

-anyFolder
--test.html
--test.js
--test2.js

test.html的内容:

<html>
   <head>
       <script type="text/javascript" src="test.js"></script>
       <script type="text/javascript" src="test2.js"></script>
   </head>
</html>

test.js 的内容:

console.log('test before');
throw('foo');
console.log('test after');

test2.js的内容:

console.log('test 2');

打开 test.html 时的输出(在控制台中):

test before test.js:1
Uncaught foo test.js:2
test 2 

从这个测试中,您可以看到 test2.js 仍然运行,即使 test.js 抛出错误。但是,test.js 在遇到错误后会停止执行。

于 2012-10-25T13:16:55.167 回答
3

我不确定语法错误,但您可以使用它try {} catch(e) {}来捕获错误并保持其余代码运行。

不会跑到最后

var json = '{"name:"John"'; // Notice the missing curly bracket }

// This probably will throw an error, if the string is buggy
var obj = JSON.parse(json);

alert('This will NOT run');

会跑到最后

var json = '{"name:"John"'; // Notice the missing curly bracket }

// But like this you can catch errors
try {
  var obj = JSON.parse(json);
} catch (e) {
  // Do or don'
}

alert('This will run');

更新

我只是想展示如何确保在发生错误时执行其余代码。

有什么原因需要立即执行一段代码,即不将其包装到事件中?

表现。每个这样的事件都会填满事件队列。不是说会很痛,只是没必要……如果现在可以做,为什么还要以后做呢?例如 - 浏览器检测和东西。

它会显着影响性能吗?

如果你每秒这样做很多次,那么是的。

是否有另一种/更好的方法来实现相同而不是使用文档就绪事件?

是的。见上面的例子。

如果所有脚本都只是将其代码注册为事件处理程序,是否可以定义脚本的执行顺序?事件处理程序是否按照它们注册的顺序执行?

是的,我很确定他们是。

于 2012-10-25T12:31:53.200 回答