我刚刚学到了一个关于javascript执行的重要事实,如果出现错误。在我开始对此做出结论之前,我最好先核实一下我是对的。
给定一个包含2个脚本的HTML页面:
<script src="script1.js" />
<script src="script2.js" />
SCRIPT1:
doSomething();
SCRIPT2:
doSomeOtherThing();
这有效地导致单个脚本作为一个单元处理:
doSomething();
doSomeOtherThing();
特别是,如果doSomething
抛出错误,则执行被破坏。 'script2'永远不会被执行。
这是我的“第1课” - 有人可能会认为,因为它是一个单独包含的文件,所以它不受script1的影响。 但确实如此。 =&gt;请参阅下面的“延迟更新”
现在,如果我们按如下方式更改script2(假设我们在上面某处包含了jQuery):
$(document).ready({ doSomeOtherThing(); });
并将脚本放在script2:
之前<script src="script2.js" />
<script src="script1.js" />
执行的顺序实际上仍然是“doSomeO((有时)'doSomeOtherThing()'后面的'doSomething()'。
然而,它以两个“单位”执行:
doSomething
作为文档的java脚本doSomeOtherThing
。如果doSomeOtherThing
引发异常,则不会破坏第二个处理“单位”。
(我不使用术语thread
,因为我认为所有脚本通常都是由同一个线程执行的,或者更确切地说,这可能取决于浏览器。)
所以,我的 Lession 2 :即使JavaScript错误可能阻止任何后续脚本执行,它也不会停止事件循环。
结论1
$(document).ready()
在定义应该独立于任何其他脚本执行的JavaScript代码块方面做得非常出色。
或者,换句话说:如果您有一段JavaScript并且想要确保即使其他脚本失败也会执行它,请将其放在$(document).ready()
内。
这对我来说是新的,因为如果脚本依赖于完全加载的文档,我只会使用该事件。
结论2
更进一步,将{em>所有脚本包装在$(document).ready()
中以确保所有脚本都“排队”执行可能是一个很好的架构决策。在上面的第二个示例中,如果<{em>} script2.js
之后包含{em},如示例1所示:
script1.js
script1.js中的错误会阻止<script src="script1.js" />
<script src="script2.js" />
被注册,因为doSomeOtherThing()
函数不会被执行。
但是,如果script1.js也使用了$(document).ready()
,那也不会发生:
$(document).ready()
两条线都将被执行。然后,事件循环将执行$(document).ready(function() { doSomething(); });
$(document).ready(function() { doSomeOtherThing(); });
,这将会中断,但doSomething
不会受到影响。
这样做的另一个原因是呈现页面的线程可以尽快返回,并且事件循环可用于触发代码执行。
批判/问题:
期待任何有用的评论!
延迟更新:
正如Briguy37正确指出的那样,我的观察一定是错误的。 (“我错了 - 是的!”)。以他的简单示例为例,我可以在所有主流浏览器中重现它,甚至在IE8中,即使script1抛出错误,也会执行script2。
仍然@ Marcello的伟大答案有助于深入了解执行堆栈的概念等。似乎两个脚本中的每一个都在一个单独的执行堆栈中执行。
答案 0 :(得分:15)
JS处理错误的方式取决于JS处理脚本的方式。它与线程没有任何关系(或很少)。因此,您必须首先考虑JS如何通过您的代码。
首先,JS会逐步读取每个脚本文件/块(此时您的代码只是看作文本)。
比JS开始解释那些文本块并将它们编译成可执行代码。如果发现语法错误,JS将停止编译并继续执行下一个脚本。在这个过程中,JS将每个脚本块/文件作为分离的实体处理,这就是为什么脚本1中的语法错误不一定会破坏脚本2的执行。代码将被解释和编译,但此时不执行,因此{ {1}}命令不会破坏执行。
在编译完所有脚本文件/块之后,JS会遍历代码(从代码中的第一个代码文件/块开始)并构建一个所谓的执行堆栈(函数调用函数b调用函数d和c)。 ...)并按给定的顺序执行它。如果在任何时候发生处理错误或以编程方式抛出(throw new Error
),则停止该堆栈的整个执行,并且JS返回到该堆栈的开头并开始执行下一个可能的堆栈
那就是说,你的onload函数在script1.js中发生错误之后仍然执行的原因,不是因为一个新的线程或其他东西而发生的,只是因为一个事件构建了一个单独的执行堆栈,JS可以跳转到,在上一个执行堆栈发生错误后发生。
回答你的问题:
有什么理由使得必须立即执行一段代码,即不将其包装到事件中?
我建议您在网络应用程序中根本没有“立即”调用代码。最佳实践是在应用程序中有一个入口点,在onload事件中调用
throw new Error('fail')
然而,这与错误处理等无关。错误处理本身绝对应该在代码内部使用条件$(document).ready(function () {App.init()});
或try / catch / finally块进行,您可能会遇到潜在的错误。这也让你有机会在catch块中尝试第二种方法,或者最终正常处理错误。
如果您可以构建应用程序事件驱动(当然不是出于错误处理原因),请执行此操作。 JS是一种事件驱动的语言,在编写事件驱动的代码时,你可以充分利用它......
它会显着影响性能吗?
事件驱动的方法可以使您的应用程序表现更好,同时使其更加稳固。事件驱动的代码可以帮助您减少内部处理逻辑的数量,您只需要进入它。
是否有其他/更好的方法来实现相同而不是使用文档就绪事件?
如前所述:try / catch / finally
如果所有脚本只将其代码注册为事件处理程序,是否可以定义脚本的执行顺序?事件处理程序是按照它们的注册顺序执行的吗?
如果在同一对象上注册同一事件,则会保留该顺序。
答案 1 :(得分:8)
您首先假设它们作为一个脚本运行是不正确的。即使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在遇到错误后停止执行。
答案 2 :(得分:2)
我不确定语法错误,但您可以使用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');
<强>更新强>
我只想说明如何确保在发生错误时执行其余代码。
有什么理由使得必须执行一部分 代码立即,即不将其包装到事件中?
性能。每个此类事件都会填满事件队列。并不是说它会伤害很多,但它只是没有必要......为什么以后做一些工作,如果它现在可以完成?例如 - 浏览器检测和填充。
它会显着影响性能吗?
如果你每秒多次这样做,那么是的。
是否有其他/更好的方法来实现相同而不是使用 文件准备活动?
是。见上面的例子。
如果只是所有脚本,都可以定义脚本的执行顺序 将他们的代码注册为事件处理程序?是事件处理程序 按照他们注册的顺序执行?
是的,我很确定它们是。