避免双重求值其实就是字符类型的js代码被运行时所产生的第二次运行求值我们都知道eval()方法是可以将字符类型的js代码运行的，但是除了它还有其他三种方法也支持。new Function()setTimeout()setInterval()这三个方法也都支持传入一个字符串类型的js代码，然后当这个函数运行后会识别这个字符并运行传入的js。这样就会产生两次求值，其中eval和Function这两种方法用的很少，甚至不使用了，而定时器确是比较常用的手段，所以为了规避这个问题，定时器里面第一个参数不要传字符，而是用函数封装。避免重复的工作在事件函数上，我们要做两个判断，要判断这个浏览器是支持dom 2级的事件还是ie的事件，所以要做判断，常用的方式如下：function addEvent(element,type,fn) { if(element.addEventListener) { element.addEventListener(type,fn,false); }else { element.attachEvent('on'+type,fn); } } function removeEvent(element,type,fn) { if(element.removeEventListener) { element.removeEventListener(type,fn,false); }else { element.detachEvent('on'+type,fn); } }这样写好像已经优化的很好了，自动判断浏览器支持，但是，事实上当我们每次运行一次addEvent函数时，都会if判断一次，然而事实上并不需要每次运行判断一次，因为用户不可能中途突然更改浏览器，所以，这个判断只需要运行一次即可。延迟加载function addEvent(element,type,fn) { if(element.addEventListener) { addEvent = function(element,type,fn){ element.addEventListener(type,fn,false); } }else { addEvent = function(element,type,fn){ element.attachEvent('on'+type,fn); } } addEvent(element,type,fn); } function removeEvent(element,type,fn) { if(element.removeEventListener) { removeEvent = function(element,type,fn){ element.removeEventListener(type,fn,false); } }else { removeEvent = function(element,type,fn){ element.detachEvent('on'+type,fn); } } removeEvent(element,type,fn); }第一次判断完重写整个函数，然后再次调用，下一次运行的时候已经是判断好的函数了，这里就可以加快运行速度。使用这种方法会在第一次调用函数时消耗较长的时间，还有另一种方式可以更快的运行。条件预加载var addEvent = document.body.addEventListener ? function(element,type,fn) { element.addEventListener(type,fn,false); } : function(element,type,fn){ element.attachEvent('on'+type,fn); }; var removeEvent = document.body.removeEventListener ? function(element,type,fn){ element.removeEventListener(type,fn,false); } : function(element,type,fn){ element.detachEvent('on'+type,fn); }; 我们知道，当js代码被下载完毕后是会被自动执行一次的，那么在这个自动执行的时候执行了三元运算，此时将判断好的函数赋值给实例，从而第一调用时就是正确的函数，没有了第一次判断所用的耗时。
异步监听readyState == 3当readyState的值等于3时，此时服务器正在与浏览器传输数据，这个时候其实是可以进行获取到responseText值得，但是这个值并不完整，只有当readyState == 4时，值才是完整的。但是如果我们需要获取的内容是极其长的字符，那么就可以通过监听这个状态，然后轮询的方式，可以在内容没有全部下载来之前就可以提前更新内容了，这样就可以避免在下载的过程中产生的等待时间。var req = new XMLHttpRequest(); var getLastInterval =null, lastLength = 0; req.open('GET','xxx.php',true); req.onreadystatechange = readStateHandler; req.send(null); function readStateHandler() { if(red.readyStare == 3 && getLastInterval == null) { getLastInterval = setInterval(function(){ getLastePacket(); },15) } if(red.readyStare == 4) { clearInterval(getLastInterval); getLastePacket(); } } function getLastePacket() { var length = req.responseText.length; var packet = req.responseText.substring(lastLength,length); callback(packet); lastLength = length; } 当readyState等于3的时候，创建一个定时器，15ms调用一次getLastePacket函数，这个函数每次运行先得到当前req.responseText.length的字符长度，然后通过string的substring方法截取这段内容，然后下次运行的时候又从上一次的最后一个位置开始，然后回调函数callback，将packet字符串传入，这个回调函数还要根据你传入的值做判断，毕竟你需要判断目前的字符最后一项是否完整，如果不完整可以创建一个全局变量来保存，下次再续上。通过这个方法可以在较大的内容时减少等待时间，增加用户体验。multipart XHR说白了就是通过php将多个地址合并成一个字符串返回给浏览器，浏览器通过js将返回的字符串解析成需要的值。比如php返回三个img图片的地址，通过;分号连接。if(req.readyState == 4) { splitImage(req.responsText); } function splitImage(text) { var imgelement = null, imgdata = text.split(';'); for(var i =0,len = imgdata.length;i<len;i++){ imgelement = document.createElement('img'); imgelement.src = imgdata[i]; document.getElementsByTagName('body')[0].appendChild(imgelement); } }以此类推，我们还可以增加更多的值，比如添加css地址，js文件，通过用不同的字符分隔，就可以判断到不同值，然后对应的获取。发送数据通过ajax发送数据其实有点奢侈，但是ajax支持pos方式发送，但是如果只是单方面的发送，且数据不会太大就可以使用get方式，但是ie对get方式的请求是有字符限制的，也就是说url的字符不能超过2048个字符，为此我要可以根据实际的使用情况使用对应的方法。发送数据我们甚至可以使用动态插入script的方式，更深层的讲，我们是通过元素的src属性进行发送的。这里暂时先不讲src的方式，这里完善一下ajax的方式事实上我们还要考虑如果发送错误的话怎么处理，这里就要使用到error事件。req.onerror = functuon() { //发生错误，我们在这个函数了再次调用封装的ajax函数，重新再运行一次，也就是递归 }我们甚至可以使用setTimeout来增加延时机制，如果获取的数据是要等一段时间才能获取到，然后再发送给服务器，那么就可以onerror事件里面使用setTimeout。get的方式会比post的方式发送更快，get只有一个数据包，而post至少会有两个，一个是头信息，一个是post数据。Beacons上面讲到发送数据使用的是src的属性，那么我们就可以通过图片的src属性来发送我们的数据，但是这个也有一点小问题，就是他会等待服务器返回信息，我们可以通过返回一个204 No Content状态码来阻止客户端继续等待返回的消息正文。使用Beacons其实也是有限制的，他不能发送post请求，且url的长度是有限制的。var url = 'xxx.php'; var parms = ['id=1','time=12578']; var beacons = new Image(); beacons .src = url + '?' + parms.join('&'); beacons.onload = function() { //通过服务器返回一个图片，如果宽度是1表示成功，宽度为2表示错误 if(beacons.width == 1) { }else if(beacons.width == 2) { } } beacons.onerror = function() { //如果发生错误，递归一次 }数据的格式XML的格式是以前比较通过的格式，但是太过于冗长，其内容书写和html类似，下面是一个标准的格式：<?xml version="1.0" encoding='utf-8'> <users total='2'> <user id="1"> <username>alice</username> <realname>Alice Smith</realname> <email>alice@xxx.com</email> </user> <user id="2"> <username>alice2</username> <realname>Alice Smith2</realname> <email>alice2@xxx.com</email> </user> </users> 这种格式其实实质性的内容不多，为此并不是一种很好的方式，为此可以优化一下<?xml version="1.0" encoding='utf-8'> <users total='2'> <user id="1" username="alice" realname="Alice Smith" email="alice@xxx.com"/> <user id="2" username="alice2" realname="Alice Smith2" email="alice2@xxx.com"/> </users> 通过属性值得方式可以将大量重复性的标签丢弃，从而减少文件的大小，并且js也可以更简便的获取内容。JSONjson的格式相对于xml他更倾向于数据简化，因为json你不必要像xml那样写大量的标签，json他就是一个高度集中的数据格式。那么json的优化，最简单的就是减少书写的内容，比如name的键名改为n，单个字符，或者直接省略，通过数组的方式直接保存内容，这种方式虽然对阅读不友好，但是只要按照一样的规定去写，就可以极大的提高json文件的解析和下载速度。自定义格式json虽然很好，但是如果文件内容庞大的话，还是有其他的优化方式，比如自定义的格式，我们可以在一个js文件里面保存一个对象，这个对象保存着自定义的string内容，然后内容之间通过字符分隔，比如不同的内容用:分号隔开，同一个分组的值多个的话用:冒号隔开，然后ajax获取或者动态script获取，由于是一个js文件，所以他没有json的解析步骤，这里就快于json了。然后通过js的split()方法拆成分组，然后再针对每个组值再次详细拆分获取到需要的内容，最后保存在一个数组内并返回。事实上split的速度是极快，在大量文本内容上快于json的parse。缓存ajax的get方式发出的请求才能被浏览器缓存，但这还不够，服务器还需要发送Expires 过期时间来控制缓存存在多久。为此我们可以通过创建一个对象来保存获取到的信息，这种方式适用于同一个页面需要请求多次相同的内容。
字符串连接标准的方式都是A+B通过+进行连接，如果是下面这种写法：str += 'one' + 'two';这段代码会经历四个步骤：先在内存中创建一个新的临时字符串（内容为空）one和two先进行连接并赋值给临时字符串临时字符串与str进行连接返回的值赋值给str如果我们这样写可以避免创建临时字符串str += 'one'; str += 'two';因为只有一个字符串需要添加，然后str本身就是字符串，所以只对str进行了操作，没有再去创建新的临时字符。但是这样写太过麻烦，于是可以这样写：str = str + 'one' + 'two';因为字符的操作是从=符号开始，从左到右操作，此时str本身是一个占据在内存中的字符串，所以无需创建新的临时字符，然后依次往右连接，最后赋值，为此可以加快速度。但是在ie7及更早的版本，这种方式反倒没有加速的效果，反而会增加用时，原因是ie每次连接的时候都会每进行一步就复制一次，先将str和one连接的结果复制一次，复制一次后再复制后面的two，然后再将这两个连接的结果复制一次，依次 重复，所以在旧版的ie中，使用+=反倒更快了。但是+=也不能说明就是ie的最佳方案，在大量文字连接中，+=也是很慢，但是我们可以通过数组项合并的方式提高速度。数组项合并Array.prototype.join方法是将数组中的分隔符改变成其他的值，我们可以通过去除这个分隔符达到合并数组的效果，但是这个方法实际上在现在浏览器中是一个比较慢的方法，在旧版的ie反倒可以达到意料之外的效果。var str = 'I', strs = [], num = 5000, newStr = null; while(num--) { strs.push(str); } newStr = strs.join('');这样写反倒快于用for循环str+ ‘I’的方法。当然也仅限于旧版的ie。正则表达式正则表达式的优化要首先明白他的工作原理，最主要的就是理解回溯。回溯就是当正则匹配到一定的值后会继续查找正则后面的值，比如/.*\s+/；首先正则会贪婪的将所有字符匹配到，然后进行回溯，也就是从最后一个字符往前查找，当找到一个\s是并不会立即放弃，它还会继续回溯，直到找不到\s字符了。如果是惰性模式/.*?\s+/，它的方式又不一样了，惰性可以理解为最少次数，所以他会从最开始一个一个字符回溯，找到一个\s后并不会立即停止，它还会继续查找，直到后面的字符不是\s后才会停止。使用不明确的正则容易造成回溯失控，你可以把回溯理解为路口，当你到了一个站点后，就会根据实际情况产生多个不同的选择，每个选择为一个路口，当正则在第一个路口找不到的时候就会返回到第二个路口，然后无限重复，直到找到了值或者全部路口都找不到才会停下，所以说，能找到的正则时间最短，找不到的正则是最慢的，因为他要不断的回溯。为此宽泛的正则性能往往不如更具体的选择，.*往往不如[white]这种实际指出的值，然后我们还可以通过一些位置符减少回溯的次数，比如\b、\B这种。var str = 'AAAAAAAAAAAAAAAAAAAAAAAAAA'; var reg = /A+A+B/g; var time = +new Date(); alert(reg.test(str) +' ' + (+new Date() - time))这段代码中，如果str没有最后的B字符，正则依旧会不断的重复回溯很多次，A+A+会产生众多的路口，为此我们要考虑到一种情况，就是当str中没有B的时候该怎么办？这里我们就可以使用预查?!var str = 'AAAAAAAAAAAAAAAAAAAAAAAAAA'; var reg = /A+A+(?=B)B/g; var time = +new Date(); alert(reg.test(str) +' ' + (+new Date() - time))通过这个预查可以减少回溯的点，因为预查已经判断到了A+A+的部分，后面是要有B的，那么正则就不会再一个个判断，为此减少了时间，alert输出的时候，你会发现使用之前的正则返回1毫秒的时间，而使用预查的只用了0毫秒，速度非常之快，如果在大量文本内容的时候，预查的优势也会很高，但是不能滥用，预查也是可以进行一定的优化的。捕获正则中的捕获()也就是分组会将里面的内容额外的保存起来，如果大量不用的捕获，会浪费内存的空间，为此可以使用非捕获来减少空间占用。/^[a-z]+(?:.*)/通过非捕获?:来达到减少占用。事实上预查虽然是一个()，但是他是非捕获性的，所以他不能被回调所调用。何时不使用正则其实这里主要是因为正则只能从开始匹配到末尾，没办法从末尾匹配到开头，哪怕你使用了$字符申明，但是他依旧是从前到后，所以如果是针对末尾的操作，使用其他方法会更快。比如去除文字末尾的空白字符var str = 'isdjaajda ', reg = /\s/, end = str.length - 1; while(reg.test(str.charAt(end))) { end-- ; } str = str.slice(0,end+1);string的操作方法，位置是从0开始计数的，然后获取的是该位置的左边，0 = |i;用|表示获取的位置，于是end就等于文字的length-1获得最后一位的位置，最后当end判断到不是空白字符时，end实际上等于|a这里，使用slice获取区间时要拿到最后一个a，所以end+1。slice获取到就是末尾没有空白字符的值，如果你使用正则的话想对来说会比较慢一些，特别是前面很多字符的情况。混合解决方法正则开头匹配很快，后面很慢，那么就可以混合使用，达到优化的效果function trim(str) { var str = str.replace(/^\s+/,''), end = str.length -1, reg = /\s/; while(reg.test(str.charAt(end))) { end--; } return str.slice(0,end + 1); } trim(' isdjaajda ');
循环for循环是常见的编程模式之一，也是提升性能必须要关注的要点之一。js的循环有四种：for循环while循环do--while循环for--in循环其中在浏览器中，while的循环会快于for循环，而for--in循环是最慢的，因为他会先从对象实例属性开始到从原型链继承而来的属性一一枚举。如何提高循环的性能？首先我们要知道，除了for--in，其他的循环性能都差不多，深究那种循环最快其实没有什么意义，循环的方式要看你环境的需求，那么抛开最快来讲，我们怎么才能提高循环的性能？无非就两点：每次循环处理的事物循环的次数通过减少两者中的一个，或者全部的时间开销，就可以提升整体的性能。减少循环处理的事物首先看下平时使用的循环：var item = [1,2,3] for(var i = 0;i<item.length;i++){ get(item[i]); } var j = 0; while(j<item.length){ get(item[j++]); } var k = 0; do { get(item[k++]); }while(k<item.length) function get(num){ alert(num); }上面循环每次循环时都会产生一下操作：判断循环条件时先会查找一次item元素的length属性（item.length）然后进行一次数值比较（i<item.length）比较的计算结果然后转换为布尔值（i<item.length == true）一次自增操作（++）一次数组查找（item[i]）一次函数调用（get(item[i])）这些循环虽然代码不多，但是每次循环都会产生以上的步骤，我们可以通过减少以上的步骤来达到效果之前讲过一次，局部变量的访问时比访问全局的要快的，然后将需要重复访问的值保存为一个局部变量，就可以减少访问次数。var item = [1,2,3] for(var i = 0，len = item.length;i < len;i++){ get(item[i]); } 这样写就可以减少访问item元素的次数，其他循环也是一样的写，定义一个变量保存length值即可达到一样的效果。我们还可以通过倒序的方式来提高循环性能。var item = [1,2,3]; for(var i = item.length;i--;){ get(item[i]); } 在js中，倒序循环会略微提升性能，通过倒序循环，并把后执行体（i--）放在前测条件中，这样就将两次对比变为了一次，直接判断数字是否为0；任何非0值都会自动转化为true，而0则等于false，原来是先判断小于length吗？是否为true，现在就直接就是是否为true，每次循环处理的事件又减少了，提升了性能。for(var i = item.length;i--;){ get(item[i]); } var j = item.length; while(j--){ get(item[j]); } var k = item.length -1; do { get(item[k]); }while(k--) 相对于之前的循环，这里只产生四个步骤：一次前测条件判断（i== true）一次自减操作（i--）一次数组查找（item[i]）一次函数运行（ get(item[i])）如何减少循环的次数？最广为人知的一种方式是“达夫设备 deff’s device”；它可以在一次循环中运行多次循环操作，为此‘jeff Greenberg’也被认为是将 “deff’s device”从原始的c实现移植到js的第一人。典型实现如下：var iterations = Math.floor(item.length/8), startAt = item.length % 8, i = 0; do { switch(startAt) { case 0 : get(item[i++]); case 7 : get(item[i++]); case 6 : get(item[i++]); case 5 : get(item[i++]); case 4 : get(item[i++]); case 3 : get(item[i++]); case 2 : get(item[i++]); case 1 : get(item[i++]); }; startAt = 0; }while(--iterations)iterations 判断当前元素能执行几个8次，使用Math.floor()去除小数点后的数。startAt 通过求余判断余几次。do -- while运行，先循环，switch判断余数，因为没有使用break跳出循环，所以，当case 0 判断为true的时候，它以及他后面的执行体都会运行，也就是说后面的get(item[i++])都会运行一遍，达到运行8次的效果。第二次do--while循环的时候--操作，如果只循环一次的话，iterations 为0，转化为false，但是也有个问题，如果不能完整的循环一次，iterations 为0的时候，再--操作，就等于-1，这样就会死循环下去，我个人时这样避免的：var iterations = Math.floor(item.length/8) == 0 ？1 ：Math.floor(item.length/8), startAt = item.length % 8, i = 0; do { switch(startAt) { case 0 : get(item[i++]); case 7 : get(item[i++]); case 6 : get(item[i++]); case 5 : get(item[i++]); case 4 : get(item[i++]); case 3 : get(item[i++]); case 2 : get(item[i++]); case 1 : get(item[i++]); }; startAt = 0; }while(--iterations)通过一个三元运算判断，如果等于0就返回1，这样下一次--操作就不会是负数了。实际上还有一种稍快的版本，他取消了do--while，并将余数处理和主循环分开了。var i = item.length % 8; while(i) { get(item[i--]) } i = Math.floor(item.length / 8); while(i) { get(item[i--]); get(item[i--]); get(item[i--]); get(item[i--]); get(item[i--]); get(item[i--]); get(item[i--]); get(item[i--]); }但是这样写是有问题的，不对，意思是明白的，所以我自己修正了一下，写成这样var startAt = item.length % 8, iterations = Math.floor(item.length / 8), i = 0; while(startAt) { get(item[i++]); startAt--; } while(iterations) { get(item[i++]); get(item[i++]); get(item[i++]); get(item[i++]); get(item[i++]); get(item[i++]); get(item[i++]); get(item[i++]); iterations--； }实际上这两种还是要看你循环的次数的，如果循环的次数过少，实际上没什么提示，但如果你循环1000次以上，提升就很明显了。基于函数的迭代ECMA-262 标准第四版引入了一个新的原生数组方法：forEach()。此方法会遍历一个数组的所有成员，并在每个成员上执行一个函数。要运行的函数作为参数传给forEach()，并在调用时接收三个参数，分别时：当前循环到的数组项的值、索引值、数组本身。除了当前循环到的数组项的值时必须的，其他两个都是自选项，可写可不写，看需要。例子：item.forEach(function(value,index,array){ get(value); });这个方法ie9及以上和其他浏览器都支持，这个方法相对使用普通的循环，减少了代码量，但是相对来说，调用外部方法怎么也会比普通循环慢一些，如果你的项目对速度有很大的要求，那么使用这个并不是一个很好的选择。（比普通循环慢8倍）条件语句条件判断也是编写代码不可少的一部分，条件表达式决定了js代码的运行流向，但由于不同的浏览器针对流程控制都有进行不同的优化，因此使用if-else还是switch方法，没有绝对的定论。但是如何使用if-else还是switch，最简单的方法就是通过判断条件的数量来判断，判断的条件越多，使用switch就更方便易读，主要还是看使用的环境和条件。比如：if(found) { }else { } switch(found) { case true : //代码处理 break; case false : //代码处理 break; default : //其他处理 }上面简单的判断，if会比switch方便易读，但如果我们增加判断的条件，就相反了if(color == 'red') { //代码处理 }else if(color == 'blue') { //代码处理 }else if(color == 'brown') { //代码处理 }else if(color == 'black') { //代码处理 }else { //代码处理 } switch(found) { case 'red': //代码处理 break; case 'blue': //代码处理 break; case 'brown': //代码处理 break; case 'black': //代码处理 break; default : //其他处理 }事实证明，大多数情况下，使用switch会比if要快，但是前提是当前的条件数量很大才能体现，这两个语句主要的性能区别是：当条件增加时，if--else的性能负担增加程度会比switch要多。 因此，我们自然倾向于条件少时使用if，条件多时使用switch。优化if--else最简单的方法就是将容易出现的情况写在最上面：比如5以下的数字容易出现if(value < 5) { }else if(value > 5) { }else { } 但是当条件过多时，这样写还不够，我们可以通过嵌套的方式来优化if(value == 0) { return 0; }else if(value == 1) { return 1; }else if(value == 2) { return 2; } ...... //这样不断重复的判断优化：if(value < 6) { if(value < 3) { if(value == 0) { return 0; }else if(value == 1) { return 1; }else { return 2; } }else { if(value == 3) { return 3; }else if(value == 4) { return 4; }else { return 5; } } }else { if(varlue < 8) { if(value == 6) { return 6; }else (value == 7) { return 7 } }else { if(value == 8) { return 8; }else if(vlaue == 9) { return 9; }else { return 10; } } } 通过判断范围然后嵌套再判断范围可以减少if判断的次数，比一个一个的判断要好的多。这个方法适用于有多个值域需要测试的时候，如果是不确定的随机数字，那么使用switch会更好。查找表有些时候优化条件语句的最佳方案就是避免使用if-else和switch。当有大量离散值的时候，使用查找表会比判断语句快的多。简单点来说就是将需要返回的值全部保存在一个对象中，可以是数组，也可以是键值对的形式，然后通过调用对应的下标或者键名来获取到结果，这样完全抛弃判断语句，整个过程变成对象值查询的过程，减少了不必要的开销。var arr ['1s','2s','3s']; return arr[x];递归使用递归可以把复杂的算法变得简单，比如传统的阶乘函数：function factorial(n) { if(n== 0) { return 1; }else { return n * factorial(n - 1); } }递归函数的潜在问题是终止条件不明确或者缺少终止条件会导致函数的长时间运行，并使得用户界面假死，而且递归函数还可能遇到浏览器调用栈的大小限制。当你使用了太多的递归，甚至超过浏览器的栈的最大容量时，浏览器就会报告错误信息，并终止代码运行。IE : “Stack overflow at line x”Firefox: “Too much recursion”safari : “Maximum call stack size exceeded”opera : “Abort(control stack overflow)”chrome是唯一个不显示调用栈溢出错误信息的浏览器。当我们遇到调用栈大小限制时，第一步应该先检查代码中的递归实例，为此有两种模式需要注意：一种是上面的factorial()，即函数调用自身function recures() { recures() } recures()在发生错误时，我们可以很快的定位到，但另一种模式就不好定位了：function one() { two() } function two() { one() } one()两个函数相互调用，形成一个无限循环，这种模式很难在代码库中定位到。大多数的栈调用错误都与这两种模式有关，最常见导致溢出的原因就是不正确的终止条件，因此定位错误的原因首先就要验证终止的条件，如果终止条件没有问题，那么就可能是算法中包含了太多的递归，为此可以采用其他的方式，建议使用迭代。迭代function factorial(n) { var arr = n ; while(--n > 0) { arr = arr * n } return arr } factorial(6) 通过循环的方式代替递归，也就是迭代，迭代本身的意思就是重复反馈过程的活动，其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”，而每一次迭代得到的结果会作为下一次迭代的初始值。名字高大上，其实就是循环嘛！迭代其实常用于合并排序，由于比较难理解，我从网上找到一张非常明了的图，这个展示一下：也就是分而治之，代码如下：function mergn(left,right) { var result = []; if(left.length > 0 && right.length > 0) { if(left[0] < right[0]) { result.push(left.shift()); }else { result.push(right.shift()); } } return result.concat(left).concat(right); } function mergSort(item){ if(item.length == 1) { return item; } var middle = Math.floor(item.length / 2), left = item.slice(0,middle), right = item.slice(middle); return mergn(left,right); }当然迭代也是有优化的，针对上面这个也有对应的方式，但是我目前理解不来，后续再写吧！
dom是浏览器中非常重要的一部分，他其实相对于js是一个独立的语言，我们通过js去操作其实只是利用api沟通，并不是直接操作的，也就是说dom和js是两个部分，他们之间通过api进行沟通，那么这个沟通的过程自然就会产生性能的损耗，那么你沟通的越多，网页的响应速度就越慢。dom的访问与修改function innerHtmlLoop() { for(var i =0;i<15000;i++) { document.getElementById('box').innerHtml += 'a'; } }这里我们对元素box添加内容，添加15000个a字符，这里就进行了15000次api的沟通，这是非常消耗资源的，实际上还有效率更高的写法：function innerHtmlLoop() { var content = ''; for(var i =0;i<15000;i++) { content += 'a'; } document.getElementById('box').innerHTML += content; }我们先获取到完整的需要添加的内容，然后再一次性传入，这样就只产生了一次沟通，性能提升非常大。innerHTML 对比 dom方法通过innerHTML 创建元素速度快呢还是createElement速度快呢？目前以现在的浏览器来说其实速度已经是相差无几了，甚至dom的方法速度会快过innerHTML，但是innerHTML长盛不衰的秘诀自然是他的方便程度了，创建一个p元素我们使用innerHTML可以用极少量的代码完成，而dom则需要重复好几步，更复杂一点来说，如果是大量的内容，innerHTML无疑是完胜dom。//innerHTML document.body.innerHTML += ' 这是一个p元素 '；//dom var p = document.createElement('p'); var text = document.createTextNode('这是一个p元素'); p.appendChild(text); document.body.appendChild(p)；为了减少dom的重复性，js还提供了html dom的专门属性，比如表格的专门属性，表单的属性，所以使用这两个方法可以根据需求自行判断使用。节点克隆如果你只是想添加一个相同元素，只是内容不同，那么使用节点克隆会更有优势，虽然这个优势并不明显。使用cloneNode(Boolean);使用布尔值来判断，是否克隆该元素的子元素：true 克隆该元素内的所有子元素false 只克隆该元素本身var p = document.getElementsByTagName('p')[0]; var p2 = p.cloneNode(true); alert(p == p2) // trueHTML集合HTML集合是包含了DOM节点引用的类数组对象，他并没有数组的push()或者slice()方法，但提供了一个length属性，并且还可以通过数字索引的方式访问集合中的元素。HTML集合事实上会与文档保持连接，当文档内容发生改变时，集合也会发生改变，哪怕你只是获取length属性，这也是HTML集合低效率的源头。var div = documetn.getElementsByTagName('div'); for(var i = 0;i<div.length;i++) { document.body.appendChild(document.createElement('div')); }这段代码看上去没什么问题，实际上却是一个死循环，因为div.length是即时性的获取，当我们对文档进行添加div时，length也会+1，然后就会进入无限死循环。然而解决这个问题也很简单，我们只需要将length作为局部的缓存来进行使用即可。var div = documetn.getElementsByTagName('div'); var length = div.length； for(var i = 0;i<length ;i++) { document.body.appendChild(document.createElement('div')); }length成为了一个固定的值，不会影响到for语句的循环。事实上，如果我们多次循环引用div集合中的元素，性能损耗也会加重，办法也是一样，将里面的元素变为一个数组保存。var div = documetn.getElementsByTagName('div'); toArray(div); function toArray(arr) { for(var i = 0,a = [],length = arr.length;i<length;i++){ a[i] == arr[i]; } return a; }通过调用toArray()函数返回的数组a，运行速度会快过从div元素集合调用，但是事实上如果你只是进行一次轮询，那么就没必要使用数组去保存，因为数组保存本身就进行了一次查询损耗，所以这个使用还是要场景，但是length是可以作为一个局部缓存来提升性能。访问集合元素时使用局部变量前面只是一个for循环，如果我们要频繁的对同一个元素进行修改，怎么提升性能？var div = documetn.getElementsByTagName('div'), length = div.length, name = ''; for(var i = 0;i<length;i++) { name += documetn.getElementsByTagName('div')[i].nodeName; name += documetn.getElementsByTagName('div')[i].nodeType; name += documetn.getElementsByTagName('div')[i].tagName; } alert(name);上面这样的写法其实是较慢的方法，因为每次for循环都会去document里获取三次div集合。那么我们进行优化一下，只进行一次获取！var div = documetn.getElementsByTagName('div'), length = div.length, name = ''; for(var i = 0;i<length;i++) { name += div[i].nodeName; name += div[i].nodeType; name += div[i].tagName; } alert(name);这种方法虽然快一点，但是还要进行三次div集合里面的查询。var div = documetn.getElementsByTagName('div'), length = div.length, name = ''， element = null; for(var i = 0;i<length;i++) { element = div[i]; name += element .nodeName; name += element .nodeType; name += element .tagName; } alert(name);当我们把重复的元素作为一个局部的缓存来保存使用时，他实际上只会进行一次查询，这样就大大的提高的运行的效率。获取DOM元素当我们要对某个元素的内部子元素进性操作的时候，常常是使用childNodes来获取子节点集合，但是childNodes是会将空白节点也作为子元素保存获取，这就造成了不必要的损失，虽然我们可以通过js进行筛选，但是筛选本身就是一种损失。除了获取集合，我们还会通过判断相邻的元素来进行操作，常用的如nextSibling、firstChild来进行遍历判断。var body = documetn.body.childNodes, length = body.length, name = ''; for(var i = 0;i<length;i++) { name += body[i].nodeName; } alert(name);通过for语句遍历body的childNodes集合。var body = documetn.body, element = body.firstChild, name = ''; do { name += element.nodeName; }wile(element = element.nextSibling); alert(name);事实上使用nextSibling进行遍历会比通过childNodes速度更快。元素节点dom元素中诸如childNodes、firstChild、nextSibling这些其实并不会区分元素节点和其他类型的节点，比如空白节点，但是大多数情况下我们并不需要空白节点，因此常常需要过滤掉这些，为此我们可以使用一些现代浏览器提供的API来避免这些麻烦。属性名说明注释children获取所有子元素节点(不包含空白节点)ie6-ie8支持该属性，但是会返回元素的注释节点childElementCount子元素节点length firstElementChild第一个子元素，不包含空白 lastElementChild最后一个子元素，不包含空白 nextElementSibling下一个同级元素，不包含空白 previousElementSibling上一个同级元素，不包含空白 使用children代替childNodes会更快，因为集合项更少，并且支持度还不错。选择器API对于获取DOM中的元素，除了常用的getElementById()、getElementsByTagName()，我们还可以使用通过选择器来获取，虽然w3c有同一个getElementsByClassName()方法，但是兼容性非常不好，但是我们有一个原生的DOM方法可以使用。querySelectorAll(‘class名’)；var elements = document.querySelectorAll('#menu a');elements包含一个引用列表，指向位置id=‘menu’下的所有a元素，querySelectorAll()方法使用css选择器作为参数并返回一个Nodelist----包含着匹配节点的类数组对象。注意这个方法返回的不是HTML集合，因此不会实时的链接文档，这也避免了之前HTML集合所带来的性能问题。如果不使用querySelectorAll()，那么代码会更长一些var elements = document.getElementById('menu').getElementsByTagName('a');这种情况下返回的是一个HTML集合，要想达到querySelectorAll()的效果，你还需要使用之前的toArry()方法将里面的元素保存在数组中去。querySelectorAll()还支持组合查询，比如查找div元素中class为‘warning’和‘notice’的元素。var elements = document.querySelectorAll('div.warning,div.notice');如果不使用querySelectorAll()来获取，代码会复杂的多。var elements = document.getElementsByTagName('div'), length = elements.length, arr = [], className = ''; for(var i =0;i<length;i++) { className = elements[i].className; if(className == 'warning' || className == 'notice') { arr.push(elements[i]); } }querySelectorAll()方法ie8支持css2选择器，ie9及以上就是css3选择器，现代浏览器全支持，所以总体来说还是一个非常不错的选择。其次的方法： querySelector()querySelector()仅仅返回匹配指定选择器的第一个元素，其他和querySelectorAll()一样，所以可以根据不同的需要进行使用。重绘与重排DOM中的元素几何属性发生改变，就会触发浏览器重排，比如改变了元素的边框大小，宽高，修改了内容导致行数增加，大小发生变化都会导致浏览器进行重排，重排完成后就会进行重绘将受影响的元素重新渲染至页面中。重排何时发生添加或删除可见的DOM元素元素位置发生改变元素尺寸发生改变内容改变，如文本改变或者图片被另一个不同尺寸的图片替换页面渲染器初始化浏览器窗口发生变化每次重排都会产生计算消耗，大多数浏览器通过队列化修改来优化重排过程，然后你可能会经常在不知不觉间强制刷新队列并要求计划任务立刻执行。比如：offsetTop(元素距离顶部的距离),offsetLeft(元素距离左边的距离),offsetWidth(可见区域宽),offsetHeight(可见区域高)scrollTop（滚动条隐藏的高度）,scrollLeft（滚动条隐藏的宽度）,scrollWidth（除去滚动条的可视宽度 == clientWidth）,scrollHeight（除去滚动条的可视高度 == clientHeight）clientTop（获取元素顶部边框的大小）,clientLeft（获取元素左边边框的大小）,clientWidth（内容的可视区域的宽度）,clientHeight（内容的可视区域高度）getComputedStyle() (或者ie的 currentStyle) 获取计算后的css样式以上属性和方法需要返回最新的值，因此浏览器不得不进行重排和重绘来返回正确的值。为此在修改样式的过程中，尽量避免使用以上的属性，因为不管你是获取还是设置都会造成浏览器的重排和重绘。最小化重排和重绘修改css值var rel = document.getElementById('mydiv'); rel.style.borderLeft = '1px'; rel.style.borderReight = '2px'; rel.style.padding = '5px';这串代码进行了三次css样式修改，改变了元素的大小，因此触发了重排，虽然现在的浏览器进行了队列优化，但是量大还是会有损失，在旧版浏览器尤为明显，而且这段代码进行了四次访问dom，并且可以优化的。var rel = document.getElementById('mydiv'); rel.style.cssText += 'border-left:1px;border-right:2px;padding:5px;'通过cssText属性可以批量的写入css属性，这样就只会修改一次dom。当然我们还可以通过修改class来达到一样的效果。批量修改DOM当你需要对DOM元素进行一系列操作的时候，可以通过以下几个步骤来减少重绘和重排次数是元素脱离文档流对其进行多重改变将元素带回文档中该过程只会触发两次重排，第一步和第三步的时候，如果你不进行脱离文档流，那么在第二步的时候就会触发多次重排重绘。脱离文档流的方法隐藏元素，改变，重新显示使用文档片断documetElementFragment创建一个节点的副本，对副本进行操作后再替换掉原来的例子： baidu google var data = [ { "name" : "sougou", "url" : "http://sougou.com" },{ "name" : "shenma", "url" : "http://shenma.com" } ]; function appendData(element,data) { var a,li; for(var i = 0,max = data.length;i<max;i++) { a = document.createElement('a'); a.href = data[i].url; a.appendChild(document.createTextNode(data[i].name)); li = document.createElement('li'); li.appendChild(a); element.appendChild(li); } } var url = document.getElmentById('mylist'); appendData(url,data);以上方法给ul添加两个li，改变的内容和大小，而且是分两次传入，于是造成了两次重排重绘。减少的办法：var url = document.getElmentById('mylist'); url.style.display = 'none'; appendData(url,data); url.style.display = 'block';使用这种方法容易造成用于体验变差，所以我们推荐使用代码片断的方法documetElementFragment()会创建一个代码片断，这个片断就是专门完成这类任务------更新和移动节点。该代码的特性是当你将这个代码片断插入至文档时，插入的并不是本体，而是他里面的子元素。var fragment = documetElementFragment() appendData(urfragment,data); document.getElmentById('mylist').appendChild(fragment);这样写的话哪怕你插入多个元素，也只是进行了一次重排和重绘。第三种方法是拷贝一个副本。var url = document.getElmentById('mylist'); var url2 = url.cloneNode(true); appendData(url2,data); url.parentNode.replaceChild(url2,url);通过cloneNode()复制一个副本，对副本进行操作，然后在通过url的父节点将url替换成操作完成的url2。推荐尽可能的使用第二张方法，因为他所产生的DOM遍历和重排次数最少。缓存布局信息当我尝试对一个元素慢慢的从1px的位置移动到500px的位置时，常常会这样写var url = document.getElmentById('mylist'); for(var i = 0;i<500;i++) { url.style.left = url.offsetLeft + 1 + 'px'; }这种方法效率低下，因为每次循环都会触发offsetLeft 进行重排获取最新信息，更好方法是：var url = document.getElmentById('mylist'); for(var i = 0,cureent = url.offsetLeft;i<500;i++) { url.style.left = cureent++ + 'px'; }将offsetLeft保存为一个缓存的值，然后每次对这个缓存的值进行++，然后再赋值给url即可。让元素脱离动画流用展开/折叠的方式来显示和隐藏部分页面是一种常见的交互方式，通常是改变一个元素的宽高来达到效果。一般来说，重排只影响渲染树中的一小部分，但也可能影响很大的部分，甚至整个渲染树。浏览器所需要重排的次数越少，应用程序的响应速度就越快，因此当页面顶部的一个动画推移页面整个余下的部分时，会导致一次代价昂贵的大规模重排，让用户感到页面一顿一顿的。渲染树中需要重新计算的节点越多，情况就会越糟。使用以下步骤可以避免页面中大部分的重排：使用绝对定位使元素脱离文档流对元素进行操作，这时的操作是局部的重绘当动画结束后恢复定位，从而只会产生一次下移其他元素实际测试后发现并没有那么简单，因为你使用了绝对定位会影响到元素的位置变化，当元素动画完毕再恢复，这个时候会有很明显的断片，使交互不够流畅。事件委托我们通过事件冒泡来减少多余的事件函数以提高性能，因为绑定事件函数是有代价的，浏览器需要实时监听这个事件，当事件越多，也会加重页面的负担。我们知道ie只支持事件冒泡，不支持捕获，那么事件冒泡就是当你点击一个子元素时，会从内到外进行冒泡，如果父元素有相同的事件就会一起触发，那么他们实际上是共用了一个event对象，那么我们可以通过这个event对象来达到我们减少事件的需要。 百度 百度 百度 百度 在这个ul中有a元素，a元素有href就会创建对应的click事件，默认都是冒泡的，如果我们要通过ajax来进行无刷新更新的话，a元素首先就要阻止默认行为，那么一般做法就是for循环a元素，然后依次创建click事件并添加阻止默认行为preventDefault()；但是这样其实是没必要的，我们可以通过事件冒泡就可以减少这段重复性的代码。var ul = document.getElementById('menu'); ul.onclick = function(e) { var e = e || window.event; e.preventDefault(); e.stopPropagation(); var target = e.target || e.srcElement; }通过给父元素增加一个click事件，然后阻止默认行为，就可以达到阻止a元素的默认行为，因为是通过同一个event对象触发的冒泡，自然可以取消啦，然后再阻止冒泡，不触发其他父元素的事件，然后就可以为所欲为了。如果浏览器不支持ajax或者禁止js代码，我们依旧可以使用a元素的href属性平滑过渡。
JavaScript中，不同存储位置，他的读取速度是不一样的，就好像一个距离你只有一米的饮料和一个距离你十米的饮料，当然是一米的你拿起来喝的速度最快。js中有四种基本的数据存取位置：1.字面量字面量只代表自身，不存储在特定的位置，js的字面量有：字符串、数字、布尔值、对象、数组、函数、正则表达式、及特殊的null和undefined值。你可以这么理解，if(true)语句中的true布尔值就是字面量，他就是一个值，不需要命名什么的就可以用的那种。2.本地变量使用var定义的数据存储单元，被作用域影响读取快慢。3.数组元素存储在JavaScript数组中的，以数字作为索引。4.对象成员存储在JavaScript对象内部，已字符串作为索引。其中字面量和本地变量读取最快，而数组和对象读取最慢，因为浏览器需要一个个遍历直到找到对应的值。虽然现在js的运行已经很快了，但是如果是大型的项目，他的代码量非常庞大，运行的效率也会被积少成多的小延迟影响到。作用域如果一个变量是在window下的，那么他是一个全局作用域下的变量，而如果他是一个function下的变量，那么他就是这个function作用域下的变量，那么function如何访问到他的作用域下的变量，又如何访问到全局的变量，这里就要了解到作用域链了。首先肯定很多人都会以为这还不简单，既然这个变量在function里面，那么自然就可以获取到里面的变量啊：function ------> 变量然后实际的原理并不是这样，function创建后，他会有一个内部属性，这个内部属性不能通过代码访问，只能通过浏览器的JavaScript引擎访问，这个属性就是[scope].scope属性他包含一个该函数被创建的作用域中的对象的集合，也就是全局对象的集合，该集合包含了window、navigator、document...等等可访问的数据对象，函数的作用域中的每个对象也被称之为可变对象，每个可变对象都是以‘键值对’的形式存在（变量名 = 值），这种存在的形式在被查找获取的时候，称之为‘标识符解析’。当function运行的时候，会创建一个称为执行环境的内部对象，每个执行环境都是独一无二的，多次调用同一个函数就会创建多个执行环境，这里是不是有点想法，比如：函数的独立属性为啥会独立？当执行环境被创建后，他的作用域链被链接到对应的运行的函数scope属性下，当这个过程完成后，一个被称之为‘活动对象’的新对象也就创建好了，活动对象作为函数运行时的变量对象，包含了函数内所有的局部变量、命名参数、参数集合及this，也就可以理解为函数内所有的变量集合了，这也就是所谓的独立属性了。这个活动对象会被推至顶端，当函数运行完毕后，这个活动对象也随之销毁。也就是说，scope属性里的顺序从原来只有一个全局对象集合到现在的：活动对象、全局对象，活动对象顺序高于全局对象，那么这也造成了局部的属性访问速度高于全局的，也就是为什么函数里面有一个name变量，window下也有一个name变量，当你在函数里面调用的时候，会是函数里的name被输出，而不是window下的，因为函数作用域下的变量所在的对象集合优先级高于全局的。标识符解析在函数执行的过程中，每遇到一个变量，都会经历一次标识符解析，从而决定从哪里获取或存储数据，该过程搜索执行环境的作用域链，查找同名的标识符，从头部开始依次查询，如果找到运行函数所调用的标识符（可以理解为变量名），就使用对应的变量，如果没有找到，就继续搜索下一个作用域链，若是无法找到匹配的对象，那么这个标识符就会被视为未定义（浏览器报错not defined），也正是这个不断搜索的过程，影响了代码的性能。标识符解析的性能标识符解析是有代价的，事实上没有哪种计算机操作可以不产生性能开销。在执行环境的作用域链中，一个标识符他的位置越深，他的读写速度就越慢，因此，在局部变量中读写总是最快的，而读写全局变量通常是最慢的，但是现在浏览器优化的js引擎，其实影响已经不那么明显了，但是不管浏览器如何优化他的引擎，总的趋势还是这样，一个标识符所在的位置越深，他的读写就越慢。来个例子：function initUI() { var bd = document.body, links = document.getElementsByTagName('a'), i = 0, len = links.length; while(i<len) { update(links[i++]); } document.getElementById('go-bth').onclick = function(event) { start(); } bd.className = 'active'; }上面这段代码，重复调用了全局作用域中document对象，三次调用代表着三次查找，每次都要遍历整个作用域链，直到在全局作用域链中找到document，那么我们可以调整一下：function initUI() { var doc = document, bd = doc.body, links = doc.getElementsByTagName('a'), i = 0, len = links.length; while(i<len) { update(links[i++]); } doc.getElementById('go-bth').onclick = function(event) { start(); } bd.className = 'active'; }通过创建一个局部变量保存document对象，然后其他的调用只调用doc，这就相当于只查找了一次，大大的提升了性能。如果这个函数里面有十次百次这样的重复调用，那么这样写性能将大大的改善。改变作用域链一般来说，一个执行环境的作用域链是不糊改变的，但是有两个语句可以在执行时改变作用域链，第一个就是with语句。with常常用来避免书写重复的代码，在js中他是这样使用的：function initUI() { with(document) { var bd = body, links = getElementsByTagName('a'), i = 0, len = links.length; while(i<len) { update(links[i++]); } getElementById('go-bth').onclick = function(event) { start(); } bd.className = 'active'; } }这样写后可以避免重复书写document，看上去更高效了，实际上却没有想象中的那么美好。首先我们要理解下with到底做了什么才可以减少重复书写？当with运行时，执行环境的作用域链发生了改变，一个新的变量对象被创建，他包含了被传入的document对象的所有属性，这个新的可变对象被推入作用域链的头部，而函数本身的局部变量作用域链处于第二的位置，这虽然加快了document的属性访问速度，但是函数本身的局部变量却变慢了，其实得到的效果反倒并不如意。除了with还有try-catch()语句会改变作用域链，try-catch()中的catch子句具有改变作用域链的效果，当try中的代码发生错误，会自动跳到catch语句中去，然后将异常的对象，也就是包含错误信息的对象推入作用域的头部，而函数本身的局部变量将在第二个作用域下，当catch运行完毕，作用域链就会恢复原状。如何处理这个运行时作用域链被改变的影响呢？我们可以通过运行一个唯一的函数，通过这个函数来处理返回的错误信息，这样就不用担心临时改变的作用域链对代码整体的影响了。try { methodThatMightCauseAnError(); }catch(e) { handleError(e); //通过这个函数来处理错误信息 }动态作用域无论是with还是try-catch中的catch子句，或者是包含eval()的函数，他们都被认为是动态作用域。动态作用域只存在于代码执行的过程中，因此无法通过静态分析（查看代码结构）检测出来。例如：function execute(code) { eval(code); function subroutine() { return window; } var w = subroutine(); } 在上面那段代码中，w一般来说就是全局的window对象，但是变量w会随着code的值而发生改变，如下：execute('var window = {}'); 当我们传入一个变量为window的字面量对象时，通过eval将其运行，w其实就等于这个字面量对象了，这里也是要讲讲eval做了什么？eval会创建一个可变对象，该对象会将传入的字符作为属性保存，然后被推入作用域的头部，此时，w获取到的其实是作用域头部的window了。闭包、作用域和内存我们先看一段代码：function assignEvents() { var id = 'xdi9592'; document.getElementById('sava-bth').onclick = function(event) { saveDocument(id); } } assignEvents函数运行后，给sava-bth元素创建了一个点击事件，这个事件处理函数就是一个闭包，他能访问assignEvents所属作用域的id变量，为了能够让这个闭包获取到id变量，必须创建一个特定的作用域链。当assignEvents函数运行时，一个包含变量id及其他数据的活动对象被创建，他是作用域链中的第一个对象，然后全局紧随其后，而当闭包创建后，闭包的scope属性会包含和assignEvents函数一样的作用域链引用，这就导致，当assignEvents函数运行完毕后，他的执行环境会常驻内存中，没有被销毁，这也是闭包的一大特性，但这也意味着闭包会比普通函数需要更多的内存开销，这也就是内存泄漏的根本原因，在大型的web项目中，这可能是个问题。当闭包运行时，他本身又会创建一个该作用域下对象集合的活动对象，而assignEvents函数的活动对象排在第二，全局最后，那么当我们频繁的跨作用域标识符解析，就会造成性能的损失，而解决的办法也和之前一样。将重复使用的变量作为该作用域下的局部变量即可。function assignEvents() { var id = 'xdi9592'; document.getElementById('sava-bth').onclick = function(event) { var id = id; saveDocument(id); } } 原型链带来的性能损失JavaScript中，所有的对象都是Object的实例，他会从Object继承所有基本方法，如：var book = { title = 'High Performance JavaScript', publisher : 'Yahoo! Press' } alert(book.toString()) //object Object我们并没有给book添加toString方法，但是他还是会有，这个方法就是从Object那继承的基本方法中的一个。我们还可以使用hasOwnProperty()方法判断对象是否含有对应的独立属性。 alert(book.hasOwnProperty('title')) //true alert(book.hasOwnProperty('toString')) //false这里可以更明确的判断tostring并不是book的独立方法而是继承而来的。通过in操作符可以查找原型中的方法 alert(title in book) //true alert(toStringin book) //true返回的都是true，也就是都可以查找到。那我们通过创建构造函数可以创建另一种类型的原型，该原型可以通过代码继承，这个原型链的不断继承也会不断加深变量的位置，造成性能损失。具体代码就不写了，构造函数的原型继承而已，很简单。嵌套成员由于对象成员可能包含其他的成员，例如不常见的写法：window.location.href。每次遇到点操作符，嵌套的成员就会导致js引擎搜索所有的对象，也就是每次都会运行标识符解析用来查找点操作符后面的属性，那么你嵌套的越深，读取速度就越慢。location.href的读取速度就会大于window.location.href，如果解析的不是对象的局部属性，那么还会搜索原型链中的内容，这样会花更多的时间。缓存对象成员值由于所有类似的性能问题都与对象成员有关，因此应该尽可能避免使用它们。更确切的说是应当注意，只要在有必要时使用。例如，在同一个函数中没必要多次读取同一个对象成员。 function hasEither(element,className1,className2) { return element.className == className1 || element.className == className2;这段代码中重复读取了两次element.className，我们可以创建一个局部变量来保存这个值，从而减少查询次数。 function hasEither(element,className1,className2) { var className = element.className； return className == className1 || className == className2;通常来说，在函数中如果要重复多次读取同一个对象的属性，最佳的做法就是同个一个局部变量来保存这个属性，从而减少多次查询来来的性能开销。在处理嵌套对象时，这样做会明显提升执行速度。总结：访问字面量和局部变量速度最快，而数组和对象则相对较慢。变量在作用域链的位置越深，读取速度越慢，访问时间也会越长，而全局变量是在作用域链的末尾，因此访问速度也是最慢的。避免使用with这些改变作用域链的语句，因为他会改变作用域链，同时局部变量也会移至第二的位置。嵌套的对象成员越多越影响性能，因此尽量简短或者少用。属性和方法在原型链中的位置越深，读取速度也就越慢通常来说，将常用的跨作用域链的对象作为局部对象保存使用，这样有利于性能的提升，因为局部的变量访问速度更快。
js的加载会阻塞页面的加载和渲染，那么常用的方式就是将script元素放置在body元素里面最底下的位置，但是这样还是会有阻塞，但是不会阻塞下载，阻塞的是渲染，也就是说，js运行长时间的话，页面也会长时间显示空白，哪怕所有的资源都已经下载完毕了。延迟脚本为此便有了无阻塞的模式，那么最简单的就是给script添加defer和async属性了，但是这两个属性当初也只有ie支持，其他的浏览器并不支持，但是目前为止，如果都是新版本的浏览器，这两个属性已经全面支持了。defer表示当前的js文件不需要修改dom，所以他会在dom加载完运行，async则是异步加载，谁先加载谁先运行，但是如果js文件有前后顺序的话，就不怎么实用了。动态脚本既然defer和async不能满足我们的需求，那么只能另寻它法，于是就有了动态脚本！动态脚本原理：在浏览器加载html的时候只有一个script元素，这个元素里的js代码非常少，代码则创建script元素并插入head中，通过代码插入的js文件并不会影响页面的加载和解析。 //js文件中的代码 function loadScript(url) { var script = document.createElement('script'); script.type = 'text/javascript'; script.src = url; document.getElementsByTagName('head')[0].appendChild(script); } loadScript('js/load.js');这样通过动态加载的js可以避开阻塞带来的问题，但是也会产生一个新的问题，如果动态加载的js文件包含其他js文件调用的接口，那么就需要判断，当这个js文件加载完毕后再运行这个接口的代码，不然浏览器就会报错。新版的浏览器支持load事件判断script元素是否加载完成，旧ie不支持，但是支持readystatechange事件，通过readyState属性返回的值判断是否加载完成。readyState的值有5个，分别为：‘uninitialized’(初始状态)、‘loading’(开始下载)、‘loaded’(下载完成)、‘interactive’(数据完成下载但还不可用)、‘complete’(所有数据准备就绪);一般来说我们只需要判断是complete即可，但是ie有时候会返回complete，有时候会返回loaded，这两个状态返回后，代码就已经可以使用了，所以我们还要检查这两种状态，只要其中任意一个出现了，就可以触发了。function loadScript(url,callback) { var script = document.createElement('script'); script.type = 'text/javascript'; if(script.readyState) { script.onreadystatechange = function() { if(script.readyState == 'loaded' || script.readyState == 'complete') { script.onreadystatechange = null； callback(); } } }else { script.onload = function() { callback(); } } script.src = url; document.getElementsByTagName('head')[0].appendChild(script); } loadScript('js/load.js');如果我们要求js1加载完再加载js2，这样顺序加载的话，可以使用callback函数，写法如下：loadScript('js/load1.js',function(){ loadScript('js/load2.js',function(){ loadScript('js/load3.js',function(){ //无限嵌套 }); }); });ajax获取通过ajax获取，可以控制代码什么时候运行，因为通过ajax获取的是js代码本身，但它并不会自动执行，只有当我们插入的时候才会运行，但是也有一个坏处，因为同源策略的限制，你无法通过js去获取cdn等资源上的js文件，当然你也可以通过跨域的方式去解决。 var xhr = new XHRHttpRequest(); xhr.open('get','js/load.js',true); xhr.onreadystatechange = function() { if(xhr.readyState == 4) { if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) { var script = document.createElment('script'); script.type = 'text/javascript'; script.text = xhr.responseText; document.body.appendChild(script); } } }; xhr.send(null);ajax还要考虑ie的兼容性，所以他的代码量还要大很多，加上同源策略，在大型的web项目上，并不会考虑ajax注入js。Lazyload 类库Lazyload类库也是通过引入外部js文件，这个js文件是经过压缩后的产物，其大小只有1.5kb，所以并不会阻塞页面，然后他也会动态插入script元素，通过通用的一个方法。 //调用代码 LazyLoad.js('js/load.js',funciton(){ Application.init(); })Lazyload 还支持多个文件，只需要传入数组即可//调用代码 LazyLoad.js(['load1.js','load2.js','load3.js'],funciton(){ Application.init(); })当然还有其他的开源库，比如LABjs....总结将script元素放入body的底部，这能保证脚本在执行前页面已经完成渲染（甚至不会阻塞文件的下载）。可以使用代码压缩和合并节约资源使用无阻塞加载的方式