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

循环

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

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

优化循环的方法有以下java

一、减小对象成员及数组项的查找次数(使用局部变量保存须要查找的对象成员)web

二、颠倒数组的顺序来提升循环性能,也就是从最后一项开始向前处理正则表达式

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

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

四、有时候咱们会想究竟是使用if-else呢仍是使用switch,事实上在大多数状况下switch比if-else运行得要快,因此当判断多于两个离散值时,switch语句是更佳的选择shell

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

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;
                    }
                }
            }

 六、使用递归虽然能够把复杂的算法变得简单,但递归函数若是终止条件不明确或缺乏终止条件会致使函数长时间运行。因此递归函数还可能会遇到浏览器“调用栈大小限制”浏览器

使用优化后的循环来替代长时间运行的递归函数能够提高性能,由于运行一个循环比反复调用一个函数的开销要少的多

若是循环资料太多,能够考虑使用以下介绍的达夫设备原理来提高性能

达夫设备

        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小结

相关文章
相关标签/搜索