`
zawa
  • 浏览: 58128 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

Javascript之UI线程与性能优化

阅读更多

    在浏览器中,Javascript执行与UI更新是发生在同一个进程(浏览器UI线程)中的。UI线程的工作基于一个简单的队列系统,任务会被保存到队列中直到进程空闲时被提取出来执行。所以Javascript的执行会阻塞UI更新;反之,UI更新也会阻塞Javascript的执行。给用户的表现就是浏览器在工作时短暂或长时间失去反应,用户的操作不能及时得到响应。而UI线程的阻塞很多时候是由于我们要在代码里进行长时间的脚本运算,超过了浏览器限制,导致浏览器失去响应,冻结用户界面。

    所以,编码时对于耗时较长的运算我们不得不考虑UI线程的问题,《High Performance JavaScript》建议javascript操作耗时不应该超过100毫秒,那么这个100毫秒是如何得出来的呢?

    我们来看下2个相关的研究:

1.1、Definitions of Response Time

    引用:Robert B. Miller:《Response time in man-computer conversational

    不同类型的响应以及响应延时,适用于不同的行为水平。响应时间是基于对心理依据的估计而定义的。Robert Miller在其研究中定义了17种不同类型的响应时间,有兴趣的同学可以细读。这里只列举2条比较重要的:

    Topic 1. Response to control activation (开关控制响应)

 

The click of the typewriter key, or the change in control force after moving a switch past a detent position are examples. They indicate responsiveness of the terminal as an object. This response should be immediate and perceived as a part of the mechanical action induced by the operator. Time delay: No more than 0.1 second.

    开关类操作中,终端相应能力对于操作者应该是直接的并可感知的。这类型的延时不应大于0.1秒。

 

 

the delay between depressing the key and the visual feedback should be no more than 0.1 to 0.2 seconds.

    按键与可视反馈延时不应大于0.1~0.2秒。

Note that this delay in feedback may be far too slow for skilled keyboard users.

    研究中同时提到,以上数值对于那些键盘高手还是很慢的。他们的预期值比一般人要高,所以以上数值只是一个普遍适用的数值。



    Topic 13. Graphic response from light pen (光标图形响应)

    
Where the lines are drawn with deliberation by the user—relatively slowly as compared with slashing sketch strokes—a delay of up to 0.1 second seems to be acceptable. There must not be variability perceived by the user in this delay.

    用户的光标输入延时,0.1秒是可被接受的。延时期间用户是动态可感知的。

    
The response delay in the image following the light pen may be as much as one second because the user is not tracing a line but positioning an image that, for him, is completed when his stylus touches the destination for the image.

    在构图中,类似的延时1秒是可以被接受的。



1.2、Response Times: The 3 Important Limits

    引用:Jakob Nielsen:《Response Times: The 3 Important Limits

 

  • 0.1 second is about the limit for having the user feel that the system is reacting instantaneously, meaning that no special feedback is necessary except to display the result.
  • 1.0 second is about the limit for the user's flow of thought to stay uninterrupted, even though the user will notice the delay. Normally, no special feedback is necessary during delays of more than 0.1 but less than 1.0 second, but the user does lose the feeling of operating directly on the data.
  • 10 seconds is about the limit for keeping the user's attention focused on the dialogue. For longer delays, users will want to perform other tasks while waiting for the computer to finish, so they should be given feedback indicating when the computer expects to be done. Feedback during the delay is especially important if the response time is likely to be highly variable, since users will then not know what to expect.


    响应时间:3条重要的限制

  • 0.1 senond : 0.1S,限制为达到用户感知系统瞬时响应,意味着无需特别反馈。
  • 1.0 senond : 1.0S,限制为用户思路不被打断,即使用户感觉到延时。通常情况下,在0.1S~1.0S间无需特别的反馈,但用户此次操作确实感觉到了兴趣上的损失。
  • 10 seconds : 10S,限制为用户在对话中的注意力极限;更长的延时会导致用户切换到其它任务以等待计算机完成当前任务。所以,在计算机完成当前任务前需要给予适当的反馈。如果响应时间是高度可变的话,延时期间的反馈是尤其重要的,因为用户不知道接下来将会发生什么。

 

    实际工作中,很多任务都不可能在100ms内完成。很多复杂的运算(例如递归与迭代),都要占用UI线程,用户的其它操作没有得到及时的响应,浏览器界面被冻结、栈溢出等情况时有发生。大部分浏览器在遇到长时间脚本运算时都会给予用户提示是否终止操作,对于很多用户来说,选择终止操作绝对是占大多数的。



2、调用栈大小限制:

    复杂的算法一般都会使用到递归,例如斐波那契序列(Fibonacci sequence), 递归函数的执行会受到浏览器调用栈大小限制(Call Stack Limits)。

    何为调用栈限制?《High Performance JavaScript》里面是这么解释的:

The amount of recursion supported by JavaScript engines varies and is directly related to the size of the JavaScript call stack. With the exception of Internet Explorer, for which the call stack is related to available system memory, all other browsers have static call stack limits. The call stack size for the most recent browser versions is relatively high compared to older browsers (Safari 2, for instance, had a call stack size of 100).

    翻译过来就是:Javascript引擎所支持的递归数量与调用栈大小直接相关。IE除外,它的调用栈大小与系统内存相关,其它浏览器都有固定的调用栈大小限制。大多数现代浏览器的调用栈大小都比老版浏览器要高(例如Safari2,其调用栈大小为100)。



    Figure 4-2. JavaScript call stack size in browsers(图4-2. 浏览器调用栈大小)


    对浏览器的调用栈深浅可以作个简单的测试:

var i = 0;
function fn() {
    fn(i++);
}
try {
    fn();
} catch (e) {
    alert(i);
}

 
    测试结果如下(我只拿了手头上有的浏览器作了个简单的测试,数据仅作参考):

Firefox 6.0 -- 9015
Chrome 14 -- 26176
Opera 11.51 -- 32631
IE7/8  -- 3064
IE6  -- 1131


    从数据来看,递归不是无限制的,不同浏览器的调用栈深浅差别是很大的。递归调用过程,系统会为每一层返回点开辟栈来存储,先是逐级扩展,再是收缩回溯,伴随递归次数的增多,消耗的资源也随之增多,最终可能造成栈溢出。所以在使用递归时,必须要有明确的递归结束条件,也叫递归出口。

    对于重复的运算可以使用Memoization技术来缓存上一次运算结果,以减少重复运算带来的性能损失。Memoization技术主要是利用了散列表(或者叫键值对)来缓存运算结果,查询表比执行函数要快来达到性能优化的目的。

    以下是 菲波拉契数列(Fibonacci sequence) 的一个简单实现:

function fibonacci(n) {
    n = parseInt(n, 10);
    if (n < 2) {
        return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
}

 
    ( 以下数据基于IE8测试)

函数 -- 调用次数(次) -- 耗时(ms)
fibonacci(10) -- 77 -- 0
fibonacci(20) -- 1891 --  16
fibonacci(30) -- 2692537 --   2344


    随着n的递增,调用次数与耗时递增明显。摒弃耗时不说,你会发现浏览器已经无法响应用户操作了,浏览器UI线程已经被阻塞了。fibonacci(40)执行的时候,IE还连续弹出“是否停止运行此脚本”提示。

    使用Memoization技术后,实现的版本如下:

 

var fibonacci = (function() {
    var cache = [1, 1];
    var fib = function(n) {
        if (n > 1) {
            for (var i = cache.length; i <= n; i++) {
                cache[i] = cache[i - 1] + cache[i - 2];
            }
        }
        return cache[n - 1];
    };
    return fib;
})();

 
    迭代取代了递归,没有了浏览器调用栈大小限制,性能上的提升是非常明显的。有兴趣的TX可以自己对比下,效率已经不是一个数量级的了。



3、长时间运行脚本限制

    还是上面使用Memoization技术实现的fibonacci函数,执行fibonacci(10000000),由于运算时间长,触发了浏览器长时间运行脚本限制,UI线程被阻塞了。所以,迭代中有大规模运算,100ms内完成不了的任务可以进行拆分,让javascript短暂让出UI线程控制权,以执行其他任务。

    要短暂让出UI线程控制权,可以使用setTimeout。

    setTimeout的时间精度:
    javascript中定时器是有时间精度的, IE9(非充电模式)、IE8及其以下版本的时间精度是15.6ms;IE9(充电模式)下是4ms;其他浏览器一般也是4ms,低于4ms会降低电池使用寿命。所以,setTimeout(fn, 0)并非马上执行的,其执行时机取决于时间精度。

    为了解决setTimeout在不同浏览器的时间精度问题,W3C因此引入了新的setImmediate()函数。setImmediate与setTimeout类似,setImmediate会在UI线程空闲时将任务插入到队列并执行,我们不再需要关心时间精度的影响。并且,setImmediate执行起来比setTimeout(fn, 0)要快。

    由于任务运行时间的不确定性,在迭代运算中,可以加上运算时间监控,决定此次迭代是否需要拆分任务。

    我们来做一个简单的demo,0~n的累加运算:

// o~n的累加运算
var test = function(n, callback) {
    var result = 0;
    var i = 0;
    (function() {
        var st = +new Date();
        for (; i < n; i++) {
            if ((+new Date()) - st < 100) {
                result++;
            } else {
                setTimeout(arguments.callee, 0); // 运算时间差大于100ms时,中断运算,让出UI线程控制权
                return;
            }
        }
        callback && callback();
    })();
};
test(10000000, showResult); // 1千万次累加

 
    由于额外的流程控制开销,setTimeout方式相对直接运算会消耗更多的时间,好处是UI线程不再阻塞,可以处理更多的任务。

    除了setTimeout方式,将需要长时间运算的操作放到flash里面进行也是一种解决方案,避开javascript单一线程的影响。


    Duff's device(达夫设备):

    Duff's device是一种加速循环的技巧,其思想是尽可能减少循环的执行次数。

/**
* Duff's Device
* http://home.earthlink.net/~kendrasg/info/js_opt/jsOptMain.html#duffsdevice
*/
var n = iterations / 8;
var caseTest = iterations % 8;
do {
    switch (caseTest) {
        case 0:
            testVal++;
        case 7:
            testVal++;
        case 6:
            testVal++;
        case 5:
            testVal++;
        case 4:
            testVal++;
        case 3:
            testVal++;
        case 2:
            testVal++;
        case 1:
            testVal++;
    }
    caseTest = 0;
} while (-- n > 0 );

 
    以上是Duff's device的一个实现版本。

    Duff's device将一个大循环分成每次迭代8次的小循环,8的余数再执行一次小循环,以达到减少循环的次数。

   

    HTML5 web workers:

    随着HTML5技术的发展,在浏览器UI线程外运行javascript代码成为了可能。web workers提供了一个简单的方式让javascript代码在后台线程运行而不影响UI线程。每一个web workers间都是相互独立的,都在自己的线程中运行。

var worker = new Worker('my_task.js');
worker.onmessage = function(event) { // This event handler will be called when the worker calls its own postMessage() function
    console.log("Called back by the worker!\n");
};
worker.postMessage(); // start the worker
worker.terminate(); // terminate a running worker

 
    需要注意的一点是:在web workers内不能操纵DOM,可用于处理与UI线程无关的长时间运行脚本。



一些参考文档:

 

 

2
1
分享到:
评论

相关推荐

    JavaScript应用实例-ui中的延时除了多线程有别的办法吗.js

    JavaScript应用实例-ui中的延时除了多线程有别的办法吗.js

    js 性能优化之快速响应的用户界面

    用于执行JavaScript和更新用户界面的进程通常被称为“浏览器UI线程”。JavaScript和用户界面更新在同一个进程中运行,因此一次只能处理一件事情。  ·任何JavaScript任务都不应当执行超过100毫秒,过长的运行时间...

    sanbeicha#Web-Series#跨线程消息1

    创建完毕之后,我们主要依靠 postMessage 与 onmessage 回调来在 Worker 线程与 UI 线程之间进行消息传递:// 向主线程发送消息/

    远程用户界面

    通过在后台线程上隔离应用程序代码,而在UI线程上仅保留平台本机组件,此技术可以成为强大的性能优化。 它也可以用作第三方代码以一种安全,动态,高性能的方式生成UI的方式,而无需依赖iframe。导航远程用户界面...

    JavaScript 函数节流详解及方法总结

    浏览器一个网页的UI线程只有一个,他同时会处理界面的渲染和页面JavaScript代码的执行(简单扩展一下,浏览器或者JavaScript运行大环境并不是单线程,诸如ajax异步回调、hybrid框架内与native通信、事件队列、CSS...

    Android UI组件实例集合

    Java 程序:Andorid Design Preview 工具,通过 USB 连接之后,只要简单的在计算机中选取您想要显示的程序版面范围,就可将镜像结果直接显示于手机装置之上。 10、Android的ui开发类库 GreenDroid 是一个Android的...

    Javascript的并行运算实现代码

    1 不太可能的进化 显示线程的支持 如果在Javascript中支持显示线程,那么可能是一种灾难,目前的浏览器解析Javascript并执行都是在浏览器的ui线程中工作的. 比如你可以在Javascript中运行while(true),这时浏览器界面...

    利用setTimeout解决延时执行某操作

    setTimeout,javascript 延时执行函数,闭包处理 利用javascript闭包处理延时操作

    PhantomFunctionalTest:CasperJs 的轻量级替代品,用于基于 QUnit 的功能测试,增加了与 PhantomJs 交互的有用方法,以支持 Javascript UI 测试

    CasperJs 的轻量级替代品,用于基于 QUnit 的功能测试,增加了与 PhantomJs 交互的有用方法,以支持 Javascript UI 测试 文件 安装 使用 npm,您可以使用以下命令将此框架安装到任何现有项目中: npm install pft -...

    最新Python3.5零基础+高级+完整项目(28周全)培训视频学习资料

    内置模块详解之time与datetime模块 内置模块详解之Range模块 内置模块详解之OS模块 内置模块详解之Sys模块 内置模块详解之Shelve模块 内置模块详解之Xml模块 内置模块详解之Configparser模块 内置模块详解之Hashlib...

    ReactNative移动开发工具 v0.71.7

    在幕后,React Native在主线程之外,在另一个背景线程里运行JavaScript引擎,两个线程之间通过一批量化的async消息协议来通信(有一个专门的React插件)。 UI方面React Native提供跨平台的类似Flexbox的布局系统,...

    JavaScript提高性能知识点汇总

    这里鉴于JavaScript执行和UI渲染的单线程原因,如果js文件载入会阻塞后面对于页面的解析过程,页面会等到js文件完全加载并运行后才继续执行该做的操作。那么问题就来了,这样可能会出现页面空白or卡顿现象。作为一名...

    modernUpload:使用Web Workers的Javascript上传器,因此您的UI不会冻结

    使用modernUpload ,您可以上传而不冻结UI线程! 它利用Web Workers将文件发送到您选择的服务器。 这样可以将您的文件分流到另一个进程,并允许您的用户执行其他操作。 它还支持进度条功能(这样您的用户就知道发生...

    cachewarmer:一个JavaScript工具可以预热缓存并最大程度地利用缓存

    CacheWarmer CacheWarmer建立在使用浏览器空闲状态作为游乐场来预取和缓存用户以后... 这里的主要挑战是JavaScript的单线程性质,其中UI呈现和交互之间共享同一线程。 CacheWarmer实用程序使用简单的超时技术模拟多线

    rclone-webui-react:完整的rclone云同步工具用户界面

    Rclone Web UI 最新托管版本: : 建置状态 关于 该项目正在开发中的代号2019年和2020年的谷歌夏季的下一个部分和通过 。 这是rclone cli项目@ 基于reactJS的Web UI 工作产品(GSoC 2019): 开发该项目的建议: ...

    初探传说中的setImmediate函数1

    一、前言由于JavaScript程序为单线程,因此在执行长时间的操作时(如循环和递归操作)到导致UI线程长期被阻塞,无法响应用户操作请求(如点击按钮等),让用户

    qr-scanner:轻量级 Javascript 二维码扫描器

    在 WebWorker 中运行,它保持主 / UI 线程响应。 可以配置为在彩色二维码上获得更好的性能。 根据该项目的扫描仪引擎的检测率大约是最流行的 javascript QR 扫描仪库之一2-3 倍(最多 8 倍)。 此外,另一个库经常...

    跟我学习javascript解决异步编程异常方案

    异步I/O、事件驱动使得单线程的JavaScript得以在不阻塞UI的情况下执行网络、文件访问功能,且使之在后端实现了较高的性能。然而异步风格也引来了一些麻烦,其中比较核心的问题是: 1、函数嵌套过深 JavaScript的异步...

    深入理解 Event Loop事件循环机制

    javascript从诞生之日起就是一门单线程的非阻塞的脚本语言。这是由其最初的用途来决定的:与浏览器交互。 首先等来了解js为什么是单线程的吧! 首先要明白线程的概念? 进程是资源分配的最小单位,线程是进程的一...

    quickbase-webworker:一组Webworker可以在单独的线程中运行,因为Quickbase无法

    Quickbase的javascript SDK很方便,但是可以在主UI线程上运行。 这会使您可能创建的任何代码页陷入困境,尤其是在涉及大数据时。 因此,让Web Workers在另一个线程中处理API调用! Web Worker是HTML 5的功能,因此...

Global site tag (gtag.js) - Google Analytics