高性能JavaScript笔记二(算法和流程控制、快速响应用户界面、Ajax)

循环

在javaScript中的四种循环中(for、for-in、while、do-while),只有for-in循环比其它几种明显要慢,另外三种速度区别不大

有一点需要注意的是,javascript没有块级作用域,只有函数级作用域,也就是说在for循环初始化中的var语句会创建一个函数级变量而非循环级变量

优化循环的方法有如下

1、减少对象成员及数组项的查找次数(使用局部变量保存需要查找的对象成员)

2、颠倒数组的顺序来提高循环性能,也就是从最后一项开始向前处理

        for (var i = arr.length-1; i >= 0 ; i--) {
            //process
        }

3、相信大家都会尽可能的使用for循环而非jQuery的each来遍历数组,那是因为jQuery的each方法是基于函数的迭代。尽管基于函数的迭代提供了一个更为便利的迭代方法,但它比基于循环的迭代在慢许多。

4、有时候我们会想到底是使用if-else呢还是使用switch,事实上在大多数情况下switch比if-else运行得要快,所以当判断多于两个离散值时,switch语句是更佳的选择

5、优化if-else最简单的方法就是确保最可能出现的条件放在首位,另外一个方法就是优化条件判断的次数,看下面的代码您就懂了

if (value == 0) {
                return result0;
            } else if (value == 1) {
                return result1;
            } else if (value == 2) {
                return result2;
            } else if (value == 3) {
                return result3;
            } else if (value == 4) {
                return result4;
            } else if (value == 5) {
                return result5;
            } else if (value == 6) {
                return result6;
            } else if (value == 7) {
                return result7;
            } else if (value == 8) {
                return result8;
            } else if (value == 9) {
                return result9;
            } else if (value == 10) {
                return result10;
            }

下面这种方法就是使用二分搜索法将值域分成一系列区间,然后逐步缩小区范围,对上面的例子进行的优化

            if (value < 6) {
                if (value < 3) {
                    if (value == 0) {
                        return result0;
                    } else if (value == 1) {
                        return result1;
                    } else {
                        return result2;
                    }
                } else {
                    if (value == 3) {
                        return result3;
                    } else if (value == 4) {
                        return result4;
                    } else {
                        return result5;
                    }
                }
            } else {
                if (value < 8) {
                    if (value == 6) {
                        return result06;
                    } else if (value == 7) {
                        return result7;
                    }
                } else {
                    if (value == 8) {
                        return result8;
                    } else if (value == 9) {
                        return result9;
                    } else {
                        return result10;
                    }
                }
            }

 6、使用递归虽然可以把复杂的算法变得简单,但递归函数如果终止条件不明确或缺少终止条件会导致函数长时间运行。所以递归函数还可能会遇到浏览器“调用栈大小限制”

使用优化后的循环来替代长时间运行的递归函数可以提升性能,因为运行一个循环比反复调用一个函数的开销要少的多

如果循环资料太多,可以考虑使用如下介绍的达夫设备原理来提升性能

达夫设备

        var iterations = Math.floor(items.length / 8),
            startAt = items.length % 8,
            i = 0;
        do {
            //每次循环最多可调用8次process
            switch (startAt) {
                case 0: process(items[i++]);
                case 7: process(items[i++]);
                case 6: process(items[i++]);
                case 5: process(items[i++]);
                case 4: process(items[i++]);
                case 3: process(items[i++]);
                case 2: process(items[i++]);
                case 1: process(items[i++]);
            }
            startAt = 0;
        } while (--iterations);
        var i = items.length % 8;
        while (i) {
            process(items[i--]);
        }
        i = Math.floor(items.length / 8);
        while (i) {
            process(items[i--]);
            process(items[i--]);
            process(items[i--]);
            process(items[i--]);
            process(items[i--]);
            process(items[i--]);
            process(items[i--]);
            process(items[i--]);            
        }

Memoization

避免重复是Memoization的核心思想,它缓存前一次计算结果供后续使用,下面代码就是利用缓存结果的思想计算阶乘的

        function memFactorial(n) {
            if (!memFactorial.cache) {
                memFactorial.cache = {
                    "0": 1,
                    "1": 1
                };
            }
            if (!memFactorial.cache.hasOwnProperty(n)) {
                memFactorial.cache[n] = n * memFactorial(n - 1);
            }
            return memFactorial.cache[n];
        }

写成通用方法如下代码所示:

        function memoize(fundamental, cache) {
            cache = cache || {};
            var shell = function (arg) {
                if (!cache.hasOwnProperty(arg)) {
                    cache[arg] = fundamental(arg);
                }
                return cache[arg];
            }
            return shell;
        }
    //下面是调用示例
        function factorial(n) {
            if (n==0) {
                return 1;
            }else{
                return n*factorial(n-1);
            }
        }
        var memfactorial = memoize(factorial, { "0": 1, "1": 1 });
        memfactorial(6);

 

算法和流程控制小结

字符串优化

        str += "one" + "two";
        //以下代码分别用两行语句直接附加内容给str,从而避免产生临时字符串 性能比上面提升10%到40%;
        str += "one";
        str += "two";
        //同样你可以用如下一句达到上面同样的性能提升
        str = str + "one" + "two";
        //事实上 str = str + "one" + "two";等价于 str = ((str + "one") + "two");

或许大家都喜欢用Array.prototype.join方法将数组中所有元素合并成一个字符串,虽然它是在IE7及更早版本浏览器 中合并大量字符串唯一高效的途径,但是事实上在现代大多数浏览器中,数组项连接比其它字符串连接的方法更慢。

在大多数情况下,使用concat比使用简单的+和+=要稍慢些。

快速响应用户界面

javascript是单线程的,共用于执行javascript和更新用户界面的进程通常被称作为“浏览器UI线程”,也就是说某一时间,UI线程只能做一件事情,要么执行javascript,要么更新用户界面,假设你当前的javascript需要执行很长时间,而这时候用户点击了界面按钮,那么UI线程则无法立即响应用户点击更新按钮UI状态,导致用户以为没点击而进行多次点击操作

所以有些浏览器会限制javascript任务的运行时间

单个javascript操作花费的总时间不应该超过100毫秒,所以应该限制所有javascript任务在100毫秒或更短的时间内完成,但是。。。

应该让出UI控制权(也就是停止执行javascript)使得UI线程有机会更新,然后再继续执行javascript

使用setTimeout和setInterval来创建定时器(定时器代码只有在创建它的函数执行完成之后,才有可能被执行)

使用setTimeout和使用setInterval几乎相同,唯一的区别在于如果UI队列中已经存在由同一个setInterval创建的任务,那么后续任务不会被添加到UI队列中

每个定时器最好使用至少25毫秒,因为更小的延时对于大多数UI更新根本不够用

        function processArray(items,process,callback) {
            var todo = items.concat();//克隆原数组
            setTimeout(function () {
                process(todo.shift());
                if (todo.length > 0) {
                    setTimeout(arguments.callee, 25);
                } else {
                    callback(items);
                }
            }, 25);
        }
        //for example
        var items = [123, 45, 443, 35, 53, 7544, 7654, 75, 75, 32, 653, 76];
        function outputValue(value) {
            console.log(value);
        }
        processArray(items, outputValue, function () {
            console.log("Done");
        });

同理,分割任务也是一样。

        function multiStep(steps,args,callback) {
            var tasks = steps.concat();//克隆数组
            setTimeout(function () {
                var task = tasks.shift();//执行下一个任务
                task.apply(null, args || []);
                //检查是否还有其它任务
                if (tasks.length > 0) {
                    setTimeout(arguments.callee, 25);
                } else {
                    callback();
                }
            }, 25);
        }
        //for example
        var tasks = [openDoc, writeText, closeDoc, updateUI];//由待执行函数组成的数组
        multiStep(tasks, [id], function () {
            console.log("Done");
        });

 相信了解过Html5的都知道,h5中引用了web worker来执行与ui更新无关的长脚本,这个也可以改善用户响应时间。具体请见我的另外博文

web worker

快速响应的用户界面小结

数据传输

有5种常用技术用于向服务器请求数据:

  1. XMLHttpRquest(XHR)
  2. Dynamic script tag insertion动态脚本注入
  3. iframes
  4. Commet
  5. Multipart XHR

XMLHttpRquest:允许异步发送和接收数据,可以在请求中添加任何头信息和参数,并读取服务器返回的所有头信息及响应文本

使用XHR时,POST和GET的对比,对于那些不会改变服务器状态,只会获取数据(这种称作幂等行为)的请求,应该使用GET,经GET请求的数据会被缓存起来,如果需要多次请求同一数据的时候它会有助于提升性能 。

只有当请求的URL加上参数的长度接近或超过2048个字符时,才应该用POST获取数据,这是因为IE限制URL长度,过长时将会导致请求的URL截断

另外需要注意的是:因为响应消息作为脚本标签的源码,所以返回的数据必须是可执行的javascript代码,所以你不能使用纯xml,纯json或其它任何格式的数据,无论哪种格式,都必须封装在一个回调函数中

使用XHR发送数据到服务器时,GET方式会更快,因为对于少量数据而言,一个GET请求往服务器只发送一个数据包,而一个POST请求至少发送两个数据包,一个装载头信息,另一个装载POST正文,POST更适合发送大量数据到服务器

Multipart XHR:允许客户端只用一个HTTP请求就可以从服务器向客户羰传送多个资源,它通过在服务器端将资源打包成一个由双方约定的字符串分割的长字符串并发送到客户端,然后用javaScript处理那个长字符串,并根据mime-type类型和传入的其它头信息解析出每个资源

multipart XHR使用了流的功能,通过监听readyState为3的状态,我们可以在一个较大的响应还没有完全接受之前就把它分段处理,这样我们就可以实时处理响应片段,这也是MXHR能大幅提升性能的主要原因

使用Multipart XHR的缺点(但是它能显著提升页面的整体性能):

  1. 获得的资源不能被浏览器缓存
  2. 老版本的IE不支持readyState为3的状态和data:URL(图片不是由base64字符串转换成二进制,而是使用data:URL的方式创建,并指定mime-type为image/jpeg    使用readyState为3是因为你不可能等所有数据都传输完成再处理,那样会很慢)

 

Beacons技术

使用javascript创建一个新的Image对象,并把src属性设置为服务器上脚本的URL,该URL包含我们要通过GET传回的键值对数据(并没有创建img元素,也没有插入DOM),服务器会接收到数据并保存起来,它需向客户端发送任何回馈信息。这种方式是给服务器回传信息最有效的方式,虽然它的优点是性能消耗很小,但它的缺点也显而易见

发送的数据长度限制得相当小

如果要接收服务器端返回的数据一种方式是监听Image对象的load事件,另外一种方式就是检查服务器返回图片的宽高来判断服务器状态

数据格式

现在xml这种数据格式已全然被json取代了,原因很多,主要原因是XML文件大小太大,解析速度慢,虽然XPath在解析xml文档时比getElementsByTagName快许多,但XPath并未得到广泛支持

JSON相对xml来说,文件体积相对更少,通用性强

JSON数据被当成另一个JavaScript文件并作为原生代码执行,为实现这一点,这些数据必须封装在一个回调函数中,这就是所谓的JSON填充(JSON with padding)JSON-P

最快的JSON格式就是使用数组形式的JSON-P

使用JSON-P必须注意安全性,因为JSON-P必须是可执行的JavaScript,它可能被任何人调用并使用动态脚本注入技术插入到网站,另一方面,JSON在eval前是无效的JavaScript,使用XHR时它只是被当作字符串获取,所以不要把任何敏感数据编码在JSON-P中。

理想的数据格式应该是只包含必要的结构,以便你可以分解出每一个独立的字段,所以自定义格式相对来说体积更小点,可以快速下载,且易于解析(只要用split函数即可),所以当你创建自定义格式时,最重要的决定之一就是采用哪种分隔符

        var rows = req.responseText.split(/\u0001/);//正则表达式作为分隔符
        var rows = req.responseText.split("\u0001");//字符串作为分隔符(更为保险)

 

数据格式总结

缓存数据

  • 在服务器,设置HTTP头信息以确保你的响应会被浏览器缓存
  • 在客户端,把获取到的信息存储到本地,从而避免再次请求

如果你希望Ajax响应能被浏览器缓存,请必须使用GET方式发出请求。设置Expires头信息是确保浏览器缓存Ajax响应最简单的方法,而且其缓存内容能跨页面和跨会话

当然也可以手工管理本地缓存,也就是直接把服务器接收到的数据缓存起来

用习惯了Ajax类库了,然后却连自己怎么写一个XMLHttpRequest都不知道了,事实上很多Ajax类库都有这样那样的局限(比如说不允许你直接访问readystatechange事件,这也意味着你必须等待完整的响应接收完毕之后才能开始使用它)所以......

Ajax小结

posted @ 2015-06-22 22:14  静逸  阅读(552)  评论(3编辑  收藏  举报