ホームページ > 記事 > ウェブフロントエンド > JavaScript の実行効率についての簡単な説明
JavaScript は非常に柔軟な言語であり、さまざまなスタイルのコードを自由に記述できます。開発プロセスでは、必然的に実行効率に違いが生じます。パフォーマンスの向上に向けて、一般的で回避しやすい問題を整理してみましょう
JavaScript 自体の実行効率
スコープチェーン、クロージャ、プロトタイプ継承、eval などの Javascript の機能と同時に、さまざまな魔法の機能を提供しますまた、時間の経過とともにさまざまな効率上の問題が発生し、不用意に使用すると実行効率の低下につながります。
コーディング プロセス中にいくつかのグローバル 変数 (ウィンドウ、ドキュメント、カスタム グローバル変数など) を使用します。これは、JavaScript スコープ チェーンを理解している人なら誰でも「グローバルへのアクセス」で知っています。ローカル スコープ内の変数を使用するには、トップレベルのスコープまでスコープ チェーン全体をレイヤーごとにたどる必要がありますが、ローカル変数へのアクセス効率はより高速かつ高くなるため、ローカル スコープで一部のグローバル オブジェクトを頻繁に使用する場合、次のようにすることができます。それをローカルスコープにインポートします。例:
//1、作为参数传入模块 (function(window,$){ var xxx = window.xxx; $("#xxx1").xxx(); $("#xxx2").xxx(); })(window,jQuery); //2、暂存到局部变量 function(){ var doc = document; var global = window.global; }
eval が string を js コードとして処理できることは誰もが知っています。eval を使用して実行されたコードの方が優れていると言われています。これを使用しない場合、eval のコードは 100 倍以上遅くなります (具体的な効率性はテストしていません。興味のある学生はテストしてみてください)
JavaScript コードは、実行前に同様の「プリコンパイル」操作を実行します。まず、アクティブなオブジェクトが現在の実行環境が作成され、var で宣言された変数がアクティブ オブジェクトのプロパティとして設定されますが、この時点ではこれらの変数の割り当てはすべて未定義であり、function で定義された関数もプロパティとして追加されますアクティブなオブジェクトの値とその値は、関数が定義されているものとまったく同じです。ただし、「eval」を使用すると、「eval」内のコード (実際には文字列) は事前にコンテキストを認識できず、事前に解析および最適化することができません。つまり、プリコンパイルされた操作を実行できません。したがって、パフォーマンスも大幅に低下します
実際、現在 eval を使用する人はほとんどいません。ここで話したいのは、eval に似た 2 つのシナリオです (new Function{}
,setTimeout
,setInterver
)
setTimtout("alert(1)",1000); setInterver("alert(1)",1000); (new Function("alert(1)"))();
上記のタイプのコード実行効率は向上します。比較的低いので、メソッドのreferenceをsetTimeoutメソッドに直接渡すことをお勧めします
var f = (function(){ var a = {name:"var3"}; var b = ["var1","var2"]; var c = document.getElementByTagName("li"); //****其它变量 //***一些运算 var res = function(){ alert(a.name); } return res; })()
の変数fの戻り値上記のコードは、関数で構成されるクロージャで返されたメソッド res をすぐに実行します。この変数は、このクロージャ内のすべての変数 (a、b、c など) への参照を保持するため、これら 2 つの変数は常にメモリ内に存在します。スペース、特に dom 要素への参照は大量のメモリを消費し、res 内の変数の値のみを使用するため、クロージャーが戻る前に他の変数を解放できます
var f = (function(){ var a = {name:"var3"}; var b = ["var1","var2"]; var c = document.getElementByTagName("li"); //****其它变量 //***一些运算 //闭包返回前释放掉不再使用的变量 b = c = null; var res = function(){ alert(a.name); } return res; })()
Js の dom 操作の効率化
Web 開発のプロセスにおいて、フロントエンドの実行効率のボトルネックとなることがよくあるのが、DOM 操作 です。DOM 操作中のパフォーマンスをできるだけ節約するにはどうすればよいでしょうか。
DOM 要素の属性 (色など) が変更されると、ブラウザは対応する要素を再描画するように render に通知します。このプロセスは再描画と呼ばれます。
変更に要素のレイアウト (width など) が含まれる場合、ブラウザーは元の属性を破棄し、再計算して結果をレンダリングに渡し、ページ要素を再描画します。このプロセスはリフローと呼ばれます。
まずドキュメントから要素を削除し、変更が完了したら要素を元の位置に戻します(要素とそのサブ要素に対して多数のリフロー操作が実行された場合)要素には 1 と 2 の 2 つのオプションがあります。メソッドの効果がより明確になります)
要素の表示を「なし」に設定し、変更が完了したら表示を元の値に変更します
複数のstyle属性を変更する場合は、置換するclassクラスを定義して複数回style属性を変更する(特定のクラスメートのオススメ用)
ページに大量の要素を追加する場合はdocumentFragmentを使用する
例えば
for(var i=0;i<100:i++){ var child = docuemnt.createElement("li"); child.innerHtml = "child"; document.getElementById("parent").appendChild(child); }
上記のコードはdomを複数回操作するため比較的非効率ですが、次の形式でdocumentFragmentを作成できます。docuemntFragmentにすべての要素を追加してもdom構造は変更されず、最後にそれをページに追加します。リフローは1回のみ実行されます
var frag = document.createDocumentFragment(); for(var i=0;i<100:i++){ var child = docuemnt.createElement("li"); child.innerHtml = "child"; frag.appendChild(child); } document.getElementById("parent").appendChild(frag);
当代码中需要多次访问元素的状态信息,在状态不变的情况下我们可以将其暂存到变量中,这样可以避免多次访问dom带来内存的开销,典型的例子就是:
var lis = document.getElementByTagName("li"); for(var i=1;i<lis.length;i++){ //*** } 上述方式会在每一次循环都去访问dom元素,我们可以简单将代码优化如下 var lis = document.getElementByTagName("li"); for(var i=1,j=lis.length ;i<j;i++){ //*** }
查找dom元素时尽量避免大面积遍历页面元素,尽量使用精准选择器,或者指定上下文以缩小查找范围,以jquery为例
少用模糊匹配的选择器:例如$("[name*='_fix']")
,多用诸如id以及逐步缩小范围的复合选择器$("li.active")
等
指定上下文:例如$("#parent .class")
,$(".class",$el)
等
使用场景:一个有大量记录的列表,每条记录都需要绑定点击事件,在鼠标点击后实现某些功能,我们通常的做法是给每条记录都绑定监听事件,这种做法会导致页面会有大量的事件监听器,效率比较低下。
基本原理:我们都知道dom规范中事件是会冒泡的,也就是说在不主动阻止事件冒泡的情况下任何一个元素的事件都会按照dom树的结构逐级冒泡至顶端。而event对象中也提供了event.target(IE下是srcElement)指向事件源,因此我们即使在父级元素上监听该事件也可以找到触发该事件的最原始的元素,这就是委托的基本原理。废话不多说,上示例
$("ul li").bind("click",function(){ alert($(this).attr("data")); })
上述写法其实是给所有的li元素都绑定了click事件来监听鼠标点击每一个元素的事件,这样页面上会有大量的事件监听器。
根据上面介绍的监听事件的原理我们来改写一下
$("ul").bind("click",function(e){ if(e.target.nodeName.toLowerCase() ==="li"){ alert($(e.target).attr("data")); } })
这样一来,我们就可以只添加一个事件监听器去捕获所有li上触发的事件,并做出相应的操作。
当然,我们不必每次都做事件源的判断工作,可以将其抽象一下交给工具类来完成。jquery中的delegate()方法就实现了该功能
语法是这样的$(selector).delegate(childSelector,event,data,function)
,例如:
$("p").delegate("button","click",function(){ $("p").slideToggle(); });
参数说明(引自w3school)
参数 | 描述 |
---|---|
childSelector | 必需。规定要附加事件处理程序的一个或多个子元素。 |
event | 必需。规定附加到元素的一个或多个事件。由空格分隔多个事件值。必须是有效的事件。 |
data | 可选。规定传递到函数的额外数据。 |
function | 必需。规定当事件发生时运行的函数。 |
Tips:事件委托还有一个好处就是,即使在事件绑定之后动态添加的元素上触发的事件同样可以监听到哦,这样就不用在每次动态加入元素到页面后都为其绑定事件了
以上がJavaScript の実行効率についての簡単な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。