确保网页应用程序的响应速度是一个重要的性能关注点。总的来说,大多数浏览器有一个单独的处理进程,它由两个任务所共享:JavaScript任务和用户界面更新任务。每一刻只有其中的一个操作得以执行,也就是说,当JavaScript任务运行时用户界面不能对输入产生反应,反之亦然。或者说,当JavaScript任务运行时,用户界面就被锁定了。管理好JavaScript运行时间对网页应用的性能很重要。
JavaScript和UI更新共享的进程通常称做浏览器UI线程。UI线程围绕着一个简单的队列系统工作,任务被保存到队列中。一旦进程空闲,队列中的下一个任务将被检索和运行。这些任务不是运行JavaScript代码,就是执行UI更新,包括重绘和重排版。此进程中最令人感兴趣的部分是每次输入均导致一个或多个任务被加入队列。
在下面示例中页面包含一个按钮,按下该按钮,屏幕上显示一个消息。
<html>
<head>
<title></title>
</head>
<body>
<button onclick=/"handleClick/">
Click Me
</button>
<script type=/"text/javascript/">
function handleClick{
var p=document.createElement(/"p/");
p.innerHTML=/"Clicked!/";
document.body.appendChild(p);
}
</script>
</body>
</html>
当页面中的按钮被单击时,将触发UI线程创建两个任务并将它们添加到队列中。第一个任务是按钮的UI更新,需要改变按钮外观以指示它被按下了;第二个任务是JavaScript运行任务,包含handleClick的代码,被运行的唯一代码就是这个方法和所有被它调用的方法。假设UI线程空闲,第一个任务被检查并运行以更新按钮外观,然后JavaScript任务被检查和运行。在运行过程中,handleClick创建了一个新的<p>元素,并追加在<body>元素上,其效果是引发另一次UI改变。也就是说,在JavaScript运行过程中,一个新的UI更新任务被添加到队列中,当JavaScript运行完之后,UI还会再更新一次。
当所有UI线程任务执行之后,进程进入空闲状态,并等待更多任务被添加到队列中。空闲状态是理想的,因为所有用户操作都会立刻引发一次UI更新。如果用户企图在任务运行时与页面交互,不仅没有即时的UI更新,而且不会有新的UI更新任务被创建和加入队列。事实上,大多数浏览器在JavaScript运行时停止UI线程队列中的任务,也就是说,JavaScript任务必须尽快结束,以免对用户体验造成不良影响。
浏览器在JavaScript运行时间上采取了限制。这是一个有必要的限制,确保恶意代码编写者无法通过无尽的密集操作锁定用户浏览器或计算机。此类限制有两个:栈尺寸限制和长时间运行脚本限制。长时间运行脚本限制有时被称做长时间运行脚本定时器或失控脚本定时器,其基本思想是浏览器记录一个脚本的运行时间,一旦到达一定限度时就终止它。当到达此限制时,浏览器会向用户显示一个对话框。
有两种方法测量脚本的运行时间。第一个方法是统计自脚本开始运行以来执行过多少语句。此方法意味着脚本在不同的机器上可能会运行不同的时间长度,可用内存和CPU速度可以影响一条独立语句运行所花费的时间。第二种方法是统计脚本运行的总时间。在特定时间内可运行的脚本数量也因用户机器性能差异而不同,但脚本总是停在固定的时间上。毫不奇怪,每个浏览器在对长时间运行脚本检查方法上略有不同。
❑IE设置默认限制为500万条语句,此限制存放在Windows注册表中,叫做HKEY_CURRENT_USERSoftwareMicrosoftInternetExplorerStylesMaxScriptStatements。
❑Firefox默认限制为10 s,此限制存放在浏览器的配置设置中(在地址栏中输入about:config),键名为dom.max_script_run_time。
❑Safari默认限制为5 s,此设置不能改变,但可以关闭,通过启动Develop菜单并选择“禁止失控JavaScript定时器”来关闭此定时限制。
❑Chrome没有独立的长时间运行脚本限制,代之以依赖它的通用崩溃检测系统来处理此类实例。
❑Opera没有长运行脚本限制,将继续运行JavaScript代码直至完成。由于Opera的结构特点,当脚本运行结束时并不会导致系统不稳定。
当浏览器的长时间运行脚本限制被触发时,不管页面上的任何其他错误处理代码,都会有一个对话框显示给用户。这是一个主要的可用性问题,因为大多数互联网用户并不精通技术,会被错误信息所迷惑,不知道应该选择哪个选项(停止脚本或允许它继续运行)。
如果脚本在浏览器上触发了此对话框,意味着脚本用太长的时间来完成任务。它还表明用户浏览器在JavaScript代码继续运行状态下无法响应输入。从开发者的观点来看,没有办法改变长运行脚本对话框的外观,不能检测到它,因此不能用它来提示可能出现的问题。显然,长运行脚本最好的处理办法是避免它们。
浏览器允许脚本继续运行直至某个固定的时间,这并不意味着可以这样做。事实上,JavaScript代码持续运行的总时间应当远小于浏览器实施的限制,以创建良好的用户体验。
如果几秒钟对JavaScript运行来说太长了,那么多长时间是适当的呢?事实证明,即使一秒钟对脚本运行来说也太长了。一个单一的JavaScript操作应当使用的总时间(最大)是100 ms。如果某接口在100 ms内响应用户输入,那么用户认为自己是在直接操作用户界面中的对象。响应时间超过100 ms意味着用户认为与接口断开了。由于UI在JavaScript运行时无法更新,因此运行时间大于100 ms就会使用户感受不到对接口的控制。
更复杂的是有些浏览器在JavaScript运行时不将UI更新放入队列。例如,在某些JavaScript代码运行时单击按钮,浏览器可能不会将重绘按钮的UI更新任务放入队列,也不会将由这个按钮启动的JavaScript任务放入队列。其结果是一个无响应的UI,表现为挂起或冻结。
每种浏览器的行为大致相同。当脚本执行时,UI不随用户交互而更新。此时JavaScript任务作为用户交互的结果被创建并放入队列,然后当原始JavaScript任务完成时队列中的任务被执行。用户交互导致的UI更新被自动跳过,因为优先考虑的是页面上的动态部分。因此,当一个脚本运行时单击一个按钮,将看不到它被按下的样子,即使它的onclick句柄被执行了。
尽管浏览器尝试在这些情况下做一些符合逻辑的事情,所有这些行为还是会导致一个间断的用户体验。因此,最好的方法是,通过限制任何JavaScript任务在100 ms或更少时间内完成,避免此类情况出现。这种测量应当在支持得最慢的浏览器上执行。