グローバル ルックアップを避ける
例:
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."; }
この関数はまったく正常に見えるかもしれませんが、グローバル ドキュメント オブジェクトへの 3 つの参照が含まれています。ページ上に複数の画像がある場合、for ループ内のドキュメント参照が複数回、場合によっては数百回実行され、そのたびにスコープ チェーン検索が実行されます。ドキュメント オブジェクトを指すローカル変数を作成すると、グローバル検索を制限することでこの関数のパフォーマンスを向上させることができます。
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."; }
ここでは、最初にドキュメント オブジェクトをローカル doc 変数に格納してから、残りのコードで元のドキュメントを置き換えます。 。元のバージョンと比較すると、この関数にはグローバル ルックアップが 1 つだけ含まれており、明らかに高速になっています。
1. 不必要な属性検索を回避します
定数値の取得は非常に効率的なプロセスです
var value = 5; var sum = 10 + value; alert(sum);
コードは、数値 5、変数値、数値 10、変数合計の 4 つの定数値検索を実行します。 。
JavaScript での配列要素へのアクセスは、単純な変数検索と同じくらい効率的です。したがって、次のコードは前の例と同じくらい効率的です:
var value = [5,10]; var sum = value[0] + value[1]; alert(sum);
その名前のプロパティの検索はプロトタイプ チェーンで実行する必要があるため、オブジェクトのプロパティの検索は変数または配列にアクセスするよりも時間がかかります。 属性の検索が多いほど、実行時間は長くなります。
var values = {first: 5, second: 10}; var sum = values.first + values.second; alert(sum);
このコードは、2 つの属性ルックアップを使用して合計の値を計算します。属性検索を 1 回または 2 回実行しても、重大なパフォーマンスの問題は発生しませんが、数百回または数千回実行すると、実行速度が確実に低下します。
単一の値を取得する複数の属性ルックアップには注意してください。例:
var query = window.location.href.substring(window.location.href.indexOf("?"));
このコードには、6 つのプロパティ検索があります: window.location.href.substring() が 3 回、window.location.href.indexOf() が 3 回です。 コード内のポイントの数を数えるだけで、ルックアップの数が決まります。 このコードでは window.location.href を 2 回使用し、同じ検索を 2 回実行するため、特に効率が悪くなります。
オブジェクトのプロパティを複数回使用した場合は、ローカル変数に保存する必要があります。以前のコードは次のように書き換えることができます:
var url = window.locaiton.href; var query = url.substring(url.indexOf("?"));
このバージョンのコードには属性検索が 4 つしかなく、元のバージョンと比較して 33% 節約されます。
一般的に言えば、アルゴリズムの複雑さを軽減できる限り、可能な限り軽減する必要があります。ローカル変数を使用して、プロパティの検索をできる限り値の検索に置き換えます。さらに、数値配列の場所または名前付きプロパティ (NodeList オブジェクトなど) でアクセスできる場合は、数値の場所を使用します。
2. ループを最適化する
ループの基本的な最適化手順は次のとおりです。
(1) デクリメント反復 - ほとんどのループは、0 から始まり特定の値まで増加する反復子を使用します。多くの場合、ループを通じて最大値から開始して反復子をデクリメントする方が効率的です。
(2) 終了条件の簡略化 - 終了条件はループ処理が実行されるたびに計算されるため、可能な限り高速であることが保証されなければなりません。これは、プロパティの検索やその他の操作を回避することを意味します。
(3) ループ本体を簡素化します - ループは最も実行されるため、最大限に最適化されていることを確認し、ループの他の集中的な計算を簡単に削除できるようにします。
(4 ポストテスト ループを使用する - 最も一般的に使用される for ループと while ループはプレテスト ループです。do-while などのポストテスト ループは、初期終了条件の計算を回避できるため、より高速に実行されます。
以下は基本的な for ループです:
for(var i=0; i < value.length; i++){ process(values[i]); }
このコードでは、変数 i が 0 から値配列内の要素の合計数まで増加します。ループは、以下に示すように i を減少させるように変更できます。
for(var i=value.length -1; i >= 0; i--){ process(values[i]); }
終了条件は、value.length 0 から簡略化されます。
次のように、ループをテスト後のループに変更することもできます:
var i=values.length -1; if (i> -1){ do{ process(values[i]) }while(--i>=0) //此处有个勘误,书上终止条件为(--i>0),经测试,(--i>=0)才是正确的 }
ここでの主な最適化は、終了条件とデクリメント演算子を組み合わせることです。 「ポストテスト」ループを使用する場合、空の配列により余分なループが発生することを確認する必要があります。 「pre-test」ループは回避できます
3. ループするときにループを展開します。はい、多くの場合、ループを削除して複数の関数呼び出しを使用する方が高速です。値の配列に要素が 3 つだけあると仮定します。各要素で process() を直接呼び出すことで、ループの設定と終了条件の処理に伴う追加のオーバーヘッドを排除でき、コードの実行を高速化できます。
//消除循环 process(values[0]); process(values[1]); process(values[2]);
ループ内の反復回数を事前に決定できない場合は、次のことを検討してください。 Duff デバイスと呼ばれる手法を使用します。Duff デバイスの基本的な概念は、反復回数が 8 の倍数であるかどうかを計算してループを分割することです。
Andrew B. King は、より高速な Duff デバイス手法を提案しました。 do-while ループを 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 回実行されます。
大規模なデータ セットの場合は、多くの時間を節約できますが、小さなデータ セットの場合は、余分なオーバーヘッドが発生しない可能性があります。同じタスクを完了するにはより多くのコードが必要ですが、大規模なデータ セットを扱っていない場合は、通常は価値がありません。
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的代码库中,这些优化的价值更大。
只要有可能,尽量使用数组和对象的字面量表达方式来消除不必要的语句。
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等。
以上就是详细介绍JavaScript最佳实践 –性能的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!