Maison >interface Web >js tutoriel >Performances pratiques de JavaScript
1. Faites attention à la portée
Évitez la recherche globale
Un exemple :
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."; }
Cette fonction peut sembler tout à fait normale, mais elle contient trois A référence à l'objet document global. S'il y a plusieurs images sur la page, la référence du document dans la boucle for sera exécutée plusieurs fois, voire des centaines de fois, et une recherche de chaîne de portée sera effectuée à chaque fois. En créant une variable locale pointant vers l'objet document, vous pouvez améliorer les performances de cette fonction en limitant une recherche globale :
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."; }
Ici, stockez d'abord l'objet document dans la variable doc locale puis dans la variable doc locale ; restant Remplacer le document original dans le code. Par rapport à la version originale, la fonction n'a désormais qu'une seule recherche globale, ce qui est nettement plus rapide.
2. Choisissez la bonne méthode
1. Évitez les recherches d'attributs inutiles
Obtenir des valeurs constantes est un processus très efficace
var value = 5; var sum = 10 + value; alert(sum);
Ceci code Quatre recherches de valeurs constantes ont été effectuées : le nombre 5, la valeur variable, le nombre 10 et la somme variable.
L'accès aux éléments d'un tableau en JavaScript est aussi efficace qu'une simple recherche de variable. Le code suivant est donc aussi efficace que l'exemple précédent :
var value = [5,10]; var sum = value[0] + value[1]; alert(sum);
Toute recherche de propriété sur l'objet prendra plus de temps que l'accès à une variable ou à un tableau, car la propriété portant ce nom doit être effectuée une fois dans le prototype. élément de recherche en chaîne. Plus il y a de recherches d’attributs, plus le temps d’exécution sera long.
var values = {first: 5, second: 10}; var sum = values.first + values.second; alert(sum);
Ce code utilise deux recherches d'attributs pour calculer la valeur de la somme. Effectuer une recherche d'attribut une ou deux fois n'entraînera pas de problèmes de performances importants, mais en effectuer des centaines ou des milliers ralentira certainement l'exécution.
Notez les recherches d'attributs multiples qui obtiennent une valeur unique. Par exemple :
var query = window.location.href.substring(window.location.href.indexOf("?"));
Dans ce code, il y a 6 recherches de propriétés : 3 fois pour window.location.href.substring() et 3 fois pour window.location.href.indexOf(). Comptez simplement le nombre de points dans le code pour déterminer le nombre de recherches. Ce code utilise window.location.href deux fois et la même recherche est effectuée deux fois, l'efficacité est donc particulièrement mauvaise.
Une fois qu'une propriété d'objet est utilisée plusieurs fois, elle doit être stockée dans une variable locale. Le code précédent peut être réécrit comme suit :
var url = window.locaiton.href; var query = url.substring(url.indexOf("?"));
Cette version du code ne comporte que 4 recherches d'attributs, ce qui permet d'économiser 33 % par rapport à la version originale.
D'une manière générale, tant que cela peut réduire la complexité de l'algorithme, il faut la réduire autant que possible. Utilisez des variables locales pour remplacer autant que possible les recherches de propriétés par des recherches de valeurs et, en outre, utilisez des emplacements numériques s'ils sont accessibles soit avec des emplacements de tableau numérique, soit avec des propriétés nommées (telles que des objets NodeList).
2. Optimiser la boucle
Les étapes d'optimisation de base d'une boucle sont les suivantes.
(1) Itération de décrémentation - La plupart des boucles utilisent un itérateur qui commence à 0 et augmente jusqu'à une valeur spécifique. Dans de nombreux cas, il est plus efficace de décrémenter un itérateur dans la boucle, en commençant par la valeur maximale.
(2) Simplifier la condition de terminaison - Étant donné que la condition de terminaison sera calculée à chaque fois que le processus de boucle est effectué, il faut s'assurer qu'il est aussi rapide que possible. Cela signifie éviter les recherches de propriétés ou autres opérations.
(3) Simplifiez le corps de la boucle - la boucle est la plus exécutée, alors assurez-vous qu'elle est optimisée au maximum et assurez-vous que certains autres calculs intensifs de la boucle peuvent être facilement supprimés.
(4 Utilisez des boucles de post-test - les plus couramment utilisées pour les boucles et les boucles while sont des boucles de pré-test. Les boucles de post-test telles que do-while peuvent éviter le calcul de la condition de terminaison initiale, donc il s'exécute plus rapidement.
Ce qui suit est une boucle for de base :
for(var i=0; i < value.length; i++){ process(values[i]); }
Dans ce code, la variable i augmente de 0 au nombre total d'éléments dans le tableau de valeurs. la boucle peut être modifiée pour décrémenter i, comme suit. Comme indiqué :
for(var i=value.length -1; i >= 0; i--){ process(values[i]); }
La condition de terminaison est simplifiée de value.length à 0. La boucle
peut également être transformée en post. -tester la boucle, comme suit :
var i=values.length -1; if (i> -1){ do{ process(values[i]) }while(--i>=0) //此处有个勘误,书上终止条件为(--i>0),经测试,(--i>=0)才是正确的 }
Ceci L'optimisation la plus importante ici est de combiner la condition de terminaison et l'opérateur de décrémentation en une seule instruction. La partie boucle a été entièrement optimisée. > N'oubliez pas que lorsque vous utilisez une boucle "post-test", vous devez vous assurer que les valeurs à traiter sont au moins égales à 1. Un tableau vide provoquera une boucle supplémentaire et la boucle "pré-test" pourra être évitée
3. Déroulez la boucle
Lorsque le nombre de boucles est déterminé, éliminez la boucle et utilisez-la plusieurs fois. Les appels de fonction sont souvent plus rapides. , en appelant process() directement sur chaque élément. Cela élimine la surcharge supplémentaire liée à la configuration de la boucle et à la gestion de la condition de terminaison, ce qui accélère l'exécution du code 🎜>
Si le nombre d'itérations dans la boucle ne peut pas être déterminé à l'avance. , vous pouvez envisager d'utiliser une technique appelée appareil Duff. Le concept de base du dispositif Duff est d'étendre une boucle en une série en calculant si le nombre d'itérations est un multiple de 8. Andrew B. King a proposé une technique de dispositif Duff plus rapide en divisant la boucle do-while en 2 boucles distinctes. Voici un exemple ://消除循环 process(values[0]); process(values[1]); process(values[2]);.
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等。