Home > Article > Web Front-end > JavaScript practical performance
1. Pay attention to the scope
Avoid global search
An example:
function updateUI(){ var imgs = document.getElementByTagName("img"); for(var i=0, len=imgs.length; i<len; i++){ imgs[i].title = document.title + " image " + i; } var msg = document.getElementById("msg"); msg.innnerHTML = "Update complete."; }
This function may look completely normal, but it contains three global documents A reference to the object. If there are multiple images on the page, the document reference in the for loop will be executed multiple times or even hundreds of times, and a scope chain search will be performed each time. By creating a local variable pointing to the document object, you can improve the performance of this function by limiting a global search:
function updateUI(){ var doc = document; var imgs = doc.getElementByTagName("img"); for(var i=0, len=imgs.length; i<len; i++){ imgs[i].title = doc.title + " image " + i; } var msg = doc.getElementById("msg"); msg.innnerHTML = "Update complete."; }
Here, first store the document object in the local doc variable; then in the remaining code Replace the original document. Compared to the original version, the function now only has one global lookup, which is definitely faster.
2. Choose the correct method
1. Avoid unnecessary attribute searches
Getting constant values is a very efficient process
var value = 5; var sum = 10 + value; alert(sum);
This code is Four constant value searches: the number 5, the variable value, the number 10 and the variable sum.
Accessing array elements in JavaScript is as efficient as simple variable lookup. So the following code is as efficient as the previous example:
var value = [5,10]; var sum = value[0] + value[1]; alert(sum);
Any property lookup on the object will take longer than accessing a variable or array, because a search for the property with that name must be done in the prototype chain . The more attribute lookups there are, the longer the execution time will be.
var values = {first: 5, second: 10}; var sum = values.first + values.second; alert(sum);
This code uses two attribute lookups to calculate the value of sum. Doing an attribute lookup once or twice won't cause significant performance issues, but doing it hundreds or thousands will definitely slow down execution.
Pay attention to multiple attribute lookups that obtain a single value. For example:
var query = window.location.href.substring(window.location.href.indexOf("?"));
In this code, there are 6 attribute lookups: 3 times for window.location.href.substring(), and 3 times for window.location.href.indexOf(). Just count the number of points in the code to determine the number of lookups. This code uses window.location.href twice, and the same search is performed twice, so the efficiency is particularly poor.
Once an object property is used multiple times, it should be stored in a local variable. The previous code can be rewritten as follows:
var url = window.locaiton.href; var query = url.substring(url.indexOf("?"));
This version of the code has only 4 attribute lookups, which saves 33% compared to the original version.
Generally speaking, as long as the complexity of the algorithm can be reduced, it should be reduced as much as possible. Use local variables to replace property lookups with value lookups as much as possible, and furthermore, use numeric positions if they can be accessed either with numeric array positions or with named properties (such as NodeList objects).
2. Optimize loop
The basic optimization steps of a loop are as follows.
(1) Decrement iteration - Most loops use an iterator that starts at 0 and increases to a specific value. In many cases, it is more efficient to decrement an iterator through the loop, starting from the maximum value.
(2) Simplify the termination condition - Since each loop process will calculate the termination condition, it must be guaranteed to be as fast as possible. This means avoiding property lookups or other operations.
(3) Simplify the loop body - the loop is the most executed, so make sure it is optimized to the maximum extent and ensure that some other intensive calculations of the loop can be easily removed.
(4 Use post-test loops - the most commonly used for loops and while loops are pre-test loops. Post-test loops such as do-while can avoid the calculation of the initial termination condition, so it runs faster .
The following is a basic for loop:
for(var i=0; i < value.length; i++){ process(values[i]); }
In this code, the variable i is incremented from 0 to the total number of elements in the values array, and the loop can be changed to decrement i, as shown below. :
for(var i=value.length -1; i >= 0; i--){ process(values[i]); }
The termination condition is simplified from value.length to 0.
The loop can also be changed into a post-test loop, as follows:
var i=values.length -1; if (i> -1){ do{ process(values[i]) }while(--i>=0) //此处有个勘误,书上终止条件为(--i>0),经测试,(--i>=0)才是正确的 }
The main optimization here is The termination condition and the decrement operator are combined into a single statement, and the loop part has been fully optimized.
Remember that when using the "post-test" loop, you must ensure that there is at least one value to be processed, and an empty array will result. The extra loop can be avoided with a "pre-test" loop
3. Unroll the loop
When the number of loops is determined, it is often faster to eliminate the loop and use multiple function calls. Assuming there are only 3 elements in the values array, calling process() directly on each element. This can eliminate the additional overhead of setting up the loop and processing the termination condition, making the code run faster. The number of iterations cannot be determined in advance, then you can consider using a technique called Duff device. The basic concept of Duff device is to expand a loop into a series of statements by calculating whether the number of iterations is a multiple of 8. Andrew B. King proposed a faster Duff device technique by splitting the do-while loop into 2 separate loops. Here is an example:
var iterations = Math.floor(values.length / 8); var leftover = values.length % 8; var i = 0; if(leftover>0){ do{ process(values[i++]); }while(--leftover > 0); } do{ process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); }while(--iterations > 0);
在这个实现中,剩余的计算部分不会在实际循环中处理,而是在一个初始化循环中进行除以8的操作。当处理掉了额外的元素,继续执行每次调用8次process()的主循环。
针对大数据集使用展开循环可以节省很多时间,但对于小数据集,额外的开销则可能得不偿失。它是要花更多的代码来完成同样的任务,如果处理的不是大数据集,一般来说不值得。
4.避免双重解释
当JavaScript代码想解析KavaScript的时候就会存在双重解释惩罚。当使用eval()函数或者是Function构造函数以及使用setTimeout()传一个字符串参数时都会发生这种情况。
//某些代码求值——避免!! eval("alert('Hello world!')"); //创建新函数——避免!! var sayHi = new Function("alert('Hello world!')"); //设置超时——避免!! setTimeout("alert('Hello world!')", 500);
在以上这些例子中,都要解析包含了JavaScript代码的字符串。这个操作是不能在初始的解析过程中完成的,因为代码是包含在字符串中的,也就是说在JavaScript代码运行的同时必须新启动一个解析器来解析新的代码。实例化一个新的解析器有不容忽视的开销,所以这种代码要比直接解析慢得多。
//已修正 alert('Hello world!'); //创建新函数——已修正 var sayHi = function(){ alert('Hello world!'); }; //设置一个超时——已修正 setTimeout(function(){ alert('Hello world!'); }, 500);
如果要提高代码性能,尽可能避免出现需要按照JavaScript解析的字符串。
5.性能的其他注意事项
(1)原生方法较快
(2)Switch语句较快
(3)位运算符较快
三. 最小化语句数
1.多个变量声明
//4个语句——很浪费 var count = 5; var color = "blue"; var values = [1,2,3]; var now = new Date(); //一个语句 var count = 5, color = "blue", values = [1,2,3], now = new Date();
2.插入迭代值
当使用迭代值的时候,尽可能合并语句。
var name = values[i]; i++;
前面这2句语句各只有一个目的:第一个从values数组中获取值,然后存储在name中;第二个给变量i增加1.这两句可以通过迭代值插入第一个语句组合成一个语句。
var name = values[i++];
3.使用数组和对象字面量
//用4个语句创建和初始化数组——浪费 var values = new Array(); values[0] = 123; values[1] = 456; values[2] = 789; //用4个语句创建和初始化对象——浪费 var person = new Object(); person.name = "Nicholas"; person.age = 29; person.sayName = function(){ alert(this.name); };
这段代码中,只创建和初始化了一个数组和一个对象。各用了4个语句:一个调用构造函数,其他3个分配数据。其实可以很容易地转换成使用字面量的形式。
//只有一条语句创建和初始化数组 var values = [13,456,789]; //只有一条语句创建和初始化对象 var person = { name : "Nicholas", age : 29, sayName : function(){ alert(this.name); } };
重写后的代码只包含两条语句,减少了75%的语句量,在包含成千上万行JavaScript的代码库中,这些优化的价值更大。
只要有可能,尽量使用数组和对象的字面量表达方式来消除不必要的语句。
四 .优化DOM交互
1.最小化现场更新
一旦你需要访问的DOM部分是已经显示的页面的一部分,那么你就是在进行一个现场更新。现场更新进行得越多,代码完成执行所花的事件就越长。
var list = document.getElementById('myList'), item, i; for (var i = 0; i < 10; i++) { item = document.createElement("li"); list.appendChild(item); item.appendChild(document.createTextNode("Item" + i)); }
这段代码为列表添加了10个项目。添加每个项目时,都有2个现场更新:一个添加li元素,另一个给它添加文本节点。这样添加10个项目,这个操作总共要完成20个现场更新。
var list = document.getElementById('myList'), fragment = document.createDocumentFragment(), item, i; for (var i = 0; i < 10; i++) { item = document.createElement("li"); fragment.appendChild(item); item.appendChild(document.createTextNode("Item" + i)); } list.appendChild(fragment);
在这个例子中只有一次现场更新,它发生在所有项目都创建好之后。文档片段用作一个临时的占位符,放置新创建的项目。当给appendChild()传入文档片段时,只有片段中的子节点被添加到目标,片段本身不会被添加的。
一旦需要更新DOM,请考虑使用文档片段来构建DOM结构,然后再将其添加到现存的文档中。
2.使用innerHTML
有两种在页面上创建DOM节点的方法:使用诸如createElement()和appendChild()之类的DOM方法,以及使用innerHTML。对于小的DOM更改而言,两种方法效率都差不多。然而,对于大的DOM更改,使用innerHTML要比使用标准DOM方法创建同样的DOM结构快得多。
当把innerHTML设置为某个值时,后台会创建一个HTML解析器,然后使用内部的DOM调用来创建DOM结构,而非基于JavaScript的DOM调用。由于内部方法是编译好的而非解释执行的,所以执行快得多。
var list = document.getElementById("myList"); html = ""; i; for (i=0; i < 10; i++){ html += "<li>Item " + i +"</li>"; } list.innerHTML = html;
使用innerHTML的关键在于(和其他的DOM操作一样)最小化调用它的次数。
var list = document.getElementById("myList"); i; for (i=0; i < 10; i++){ list.innerHTML += "<li>Item " + i +"</li>"; //避免!!! }
这段代码的问题在于每次循环都要调用innerHTML,这是极其低效的。调用innerHTML实际上就是一次现场更新。构建好一个字符串然后一次性调用innerHTML要比调用innerHTML多次快得多。
3.使用事件代理(根据第13章的概念,我认为此处应为“事件委托”更为妥当)
4.注意HTMLCollection
任何时候要访问HTMLCollection,不管它是一个属性还是一个方法,都是在文档上进行一个查询,这个查询开销很昂贵。
var images = document.getElementsByTagName("img"), image, i,len; for (i=0, len=images.length; i < len; i++){ image = images[i]; //处理 }
将length和当前引用的images[i]存入变量,这样就可以最小化对他们的访问。发生以下情况时会返回HTMLCollection对象:
进行了对getElementsByTagName()的调用;
获取了元素的childNodes属性;
获取了元素的attributes属性;
访问了特殊的集合,如document.forms、document.images等。