高性能javascript
1.大多数浏览器都使用单一进程来处理用户界面更新和javascript脚步执行,所以同一时刻只能做其中一件事情。src属性的js链接和<script>一样,浏览器必须先花时间下载外链文件中的代码,然后解析并执行,在整个过程中,页面渲染和用户交互完全阻塞。
2.浏览器在解析到<body>之前,不会渲染页面的任何部分。一般来说浏览器顺序下载并解析执行,现代浏览器也允许并行下载。但仍是单线程顺序执行。
3.推荐将所有的<script>标签尽可能放到<body>标签的底部。
4.多个js文件会增加http请求的次数,可用合并工具处理成一个文件
5.defer属性用于<script>元素在onload之前,所有脚本下载之后执行。但一般只有IE才支持
6.无阻塞脚本的三种方式:1)<script defer>在onload之前,所有脚本下载之后执行,仅IE和FireFox3.5+支持. 2)动态脚本,用createElement("script")创建,设置为src属性后append到head里。 3)XHR异步请求,将返回内容设置到所创建srcipt对象的text属性中,append到head里,实现异步记载。此方法不支持跨域。
7.推荐先载入加载其他js的脚本,然后用它来实现无阻塞加载脚本的目的,目前有一些异步加载的类库,如LazyLoad,labjs,yui3等。
8.javascript四种数据存取位置:直接量,变量,数组元素,对象成员。前两种的访问速度明显大于后两种。js运行效率受数据存取效率的影响。
9.每个函数创建时,都会初始化一个内部Scope属性,表示函数的作用域链,表明此函数可以访问到的变量,初始化一般都是一些全局对象,我们可称之为初始作用域链。而函数在真正运行时,会动态创建一个运行期上下文的内部对象ActiveObject,包含所有局部变量,命名参数,arguments以及this,此ActiveObject会被压入函数Scope链的最高层,在函数执行完后被销毁,我们可称之为ActiveObject链。函数在执行过程中解析的时候先解析此ActiveObject内的标识符,只有没找到才会继续往下找函数作用域链里的属性,这就解释了为什么访问局部变量比访问全局变量快。因此性能调优很大的一个技巧是对于需要多次访问的全局变量,如document,window等,需要用一个局部变量指代以提高访问效率。
10.改变作用域链的三种方式:1)使用with:使用with(object)后,在函数运行时,除了初始作用域链和ActiveObject链之外,还会压入一个包含object对象全部属性链,并且位于第一链,因此使用with后访问object的属性和方法会特别快,但是访问原函数中变量就反而变得慢了,因为每次访问变量也都会先从此object对象的属性链中开始找。2)catch子句:异常发生后跳到catch子句中,同时会把异常对象推入一个可变对象并置于作用域的顶部,这样做的目的当然是为了能快速访问到异常对象信息,但是访问函数其他变量就变得慢,因此不建议在catch中做过多操作。直接处理异常即可。3)闭包:常见于在函数中设置Dom元素的事件处理器,函数执行时遇到闭包代码,此闭包被创建,同时创建的还有闭包作用域链,此链就是当前执行函数的初始作用域链加上ActiveObject链。这就解释了为什么闭包可以访问所在函数的全部变量,即使所在函数已经执行完毕,但是由于有闭包仍指引了ActiveObject链,因此此链不会被正常销毁(IE中使用非原生javascript对象实现Dom,由于ActiveObject中的对象在闭包指引下不能及时销毁,会有内存泄露的危险)。同时,闭包代码在真正执行的时候类似with,会压入一个包含event,arguments等属性的对象到闭包作用域链中,这也可以解释为什么在闭包代码如事件处理代码中我们可以访问到event的属性。
11.每个对象通过一个内部属性_proto_绑定到它的原型,因此属性或方法分为对象声明成员和原型成员,用hasOwnProperty只访问对象声明成员,而用in 如 alert(name in object),对象声明成员和原型成员都会找。搜索属性或方法,如果存在比较深的原型链里,会影响性能,为提供性能,可以用局部变量暂存。
12.文档对象模型Dom本身是语言无关的,在浏览器里其接口用javascript实现。Dom渲染引擎和javascript引擎是分开的,因此用js来操作dom对象必将来回调用引擎从而产生巨大的性能问题,尤其是修改dom元素的隐显,修改元素大小都将导致页面被重新渲染。性能提高原则是尽量减少js访问dom的次数,把更多的运算在js端做完后统一发给Dom引擎处理。
13.另一方面,当浏览器下载完后页面的所有组件后,开始解析并生成Dom树表示页面结构以及渲染树表示需要显示的Dom节点。因此当我们用js改变了Dom节点的隐显或可见节点的大小,甚至访问这些节点的位置,大小属性时,浏览器为提供准确的信息,都会立马执行渲染队列中等待处理的变化,重排位置,然后重绘出来。
14.用getElements***或document.links,forms等类似的方法取HTML集合的时候,得到的是一种提供length特性及数字索引访问类似数组的集合,由于这种HTML集合与DOM实时同步,因此每次访问哪怕是访问length的时候都是重新执行查询从而影响效率。
15.提供Dom访问性能的几个诀窍:
1)用浏览器支持良好的innerHTML取代js的Dom节点操作一般来说都会加快速度.
2)用克隆已有元素取代创建新元素会提高一点效率.
3)用局部变量存储HTML集合,用局部变量存储length,用局部变量存储需要多次访问的元素。
4)用浏览器支持的children,childElementChildCount,firstElementChild等节点替换childNodes,childNodes.length,firstChild等,以过滤掉文本节点,减少判断。(只是有些属性IE不支持.) 。
5)如果要对Dom元素进行一系列操作,可以采用先让元素脱离文档流,然后操作,最后带回文档流。这样做可以减少重排和重绘元素从而提高效率。有三种方法可以使Dom元素脱离文档流,a)隐藏元素,因为不显示的元素不需要浏览器重排重绘. b)使用文档片段. c)将原始元素拷贝到一个新节点,在新节点操作完后替换原节点。
6)由于每次访问诸如偏移量,滚动位置或样式值的时候都会是UI刷新,因此将布局信息缓存到变量可以减少UI刷新从而提高性能。7)减少在具体节点进行事件委托,尽量在更高一级的节点进行,从而减少绑定事件带来的运行开销。
16.for in 循环对象属性时由于也会从原型链中搜索,会带来性能问题,而且有时候我们并不想搜索原型链中的属性。
17.普通for循环应该采用倒序循环方式,即从后往前迭代,由于任何非零数会自动转换为true,零为false,这样用倒序就减少了先获得当前index,然后和length比较的步骤,从而提高了性能。
18.if-else和switch在性能上本身并无差别,只是对于条件判断较多的情况,应该更倾向于选择switch,或者如果判断的键值之间存在某种对应关系,建立表并中表中查询是更好的选择。另外在if-else中的条件语句应该总是按照概率从最大到最小的排列,已减少中间不必要的判断从而提高性能。
19.用Duff‘s Device减少迭代的次数提高性能。基本思想是:先遍历总次数与8的取模次,然后遍历总次数除于8取整的次数,每次处理八个相同的操作。如:
var iters = Math.floor(arr.length/8); var startIn = arr.length%8; start = +new Date(); do{ switch(startIn){ case 0:process(); case 7:process(); case 6:process(); case 5:process(); case 4:process(); case 3:process(); case 2:process(); case 1:process(); } startIn = 0; }while(--iters);
20.浏览器的javascript引擎支持的递归数量与javascript调用栈大小相关,除IE外,其他所有浏览器都有固定数量的调用栈限制,IE的调用栈与系统空闲内存有关。不明确或缺少终止条件是递归最常见的问题,如果确实是递归层数量限制,可以考虑用迭代来取代递归。
21.优化递归一个很重要的手段是运用Memoization缓存已经递归出来的结果,避免重复的计算。如:
function memoize(fundamental,cache){ cache = cache || {}; var shell = function(arg){ if(!cache.hasOwnProperty(arg)){ cache[arg] = fundamental(arg); } return cache[arg]; }; return shell; }
22.str+="one"+"two"比str=str+"one"+"two"由于中间过程会创建临时变量而影响性能。而第二种做法会依次将one,two拷贝到str后。因此如果str本身比较长,就能大的提升性能,如果str不在第一位而是其他位置,就没有这种效果了。使用原生的concat也是一个选择,不过比用+和+=要慢,类似数组join方式。
23.不同浏览器对字符拼接的处理不同:
2B方式:IE7及以下,每次连接一对字符串都要把它复制到一块新分配的内存中,2B做法。不过利用数组join的方法倒是在这些浏览器下有很高的性能。
文艺方式:IE8:连接字符串只是记录现有的字符串的引用,在真正使用字符串时才将各个连接部分逐个拷贝到一个新的串中。
普通方式:其他浏览器,从等号左边第一个字符串为准,依次加到其尾部。(FireFox普通中的文艺,在编译过程中如果某次连接时都是常量字符就提前合并好,不过实战不强,因为一般来说要合并的都是运行期变量与常量的结合。)
24.理解正则表达式的回溯原理,避免回溯失败。灵活运行贪婪匹配和惰性匹配。有以下几种方法可以提高正则性能:
1)以简单必须的资源开始.
2)使用量词模式,使他们后面的字元互斥.
3)减少分支数量,缩小分支范围.
4)使用非捕获组.
5)只捕获感兴趣的文本以减少后处理.
6)明确给出必须的字元,如^或$.
7)使用合适的量词,贪婪还是惰性?
8)将正则表达式赋给变量以减少重复编译.
9)将表达式化繁为简。
25.去除字符串首尾空白时,可以采用正则表达式去除开始的空白,但是如果字符串本身很长,使用\s+$去除尾空白容易有性能影响,因此可以采用slice的方式截取,绕过长度限制。
26.浏览器UI线程既要执行javascript,又要负责更新用户界面,因此如果js脚本执行的时间过长,势必影响到界面的更新,造成死机假象,此正是单线程的悲哀。因此浏览器限制js脚本运行的时间是很有必要的。IE通过在注册表限制脚本的运行条数,默认500万条,而其他浏览器一般通过限制时间,如FireFox10秒,Safari5秒。
27.对于大段的js脚本,一般采用setTimeout和setInterval来分割时间判定,让js脚本让出UI进程已做界面的更新而不是一味的执行脚本。此两方法在被调用时开始计算延迟的时间,然后在所在方法执行结束后才可能开始执行。
28.使用定时处理器处理数组有很大的优越性,尤其是耗时很大的数组,当然前提是不要求同步处理,并且不要求数据处理顺序。每次让js脚本处理小于100毫秒如50毫秒左右的时间,然后用setTimeout使其处理过程暂停一小段时间如25毫秒以处理用户界面的更新,从而避免假死的问题,及时满足用户响应。
29.同时使用多个定时器会产生性能问题,因为只有一个UI线程。建议创建一个独立的定时器,重复使用,每次在不至于让浏览器提示脚本缓慢的前提时间下多执行一些任务以提高性能。
30.用Web Workers引入的接口可以使代码运行且不占用浏览器UI线程时间,HTML5以加入。
31.Ajax请求的几种方式:
1)XHR,是最常用的Ajax请求,其优势是可以灵活设置post和get,get方式常用于获取数据而不是改变数据,因为其有缓存,所以多次get提交相同的数据会有性能提升。不过IE限制URL的长度不超过2048个字符。此方式最大的缺点是不能跨域。
2)动态脚本注入,可以跨域,返回的js脚本直接执行,无需通过转换,处理速度很快。但是只能get方式,而且服务器必须返回可执行的js脚本且封装在一个回调函数中,无法读取头信息和响应代码,不能及时获得服务器响应。
3)Multipart XHR将多个不同类型的资源如js,css,image按约定的格式利用一次XHR下载,减少了HTTP请求的次数。
4)Beacons图片信标,利用设置new Image().src属性,将简单少量的数据传给服务器。可监听其load方法查看结果。
32.利用get方式请求的数据会被缓存起来,通过设置head信息,可以缓存请求数据从而减少了重复的请求,也可以通过存储到变量中以url为索引缓存。
33.高性能编程几点建议:
1)尽量少用eval,Function,setTimeout,setInterval函数,因为他们不仅会带来双重求值问题,而且他们每次调用时都会产生解释器/编译器实例。
2)setTimeout,setInterval第一个参数传入函数而不是字符串代码。
3)多使用直接量{name:dim,id:11}来创建对象而不是常见的object.name逐个创建。
4)对于浏览器差异函数可以采用延时加载或条件预加载的模式,在需要用到的时候才根据具体浏览器选择函数,或是在页面加载时根据浏览器不同而选择具体的函数,千万不要每次调用都去判断浏览器类型,一定要避免重复判断。
5)多使用位运算和原生方法