C/C ++と比較して、私たちが使用するJavaScriptのメモリ内のJavaScriptの処理により、開発におけるビジネスロジックの執筆にもっと注意を払っています。ただし、ビジネスの継続的な複雑さ、単一ページアプリケーションの開発、モバイルHTML5アプリケーション、node.jsプログラムなど、JavaScriptのメモリ問題によって引き起こされるラグとメモリのオーバーフローは、もはや馴染みがなくなりました。
この記事では、JavaScriptの言語レベルからのメモリの使用と最適化について説明します。誰もがよく知っている、または少し聞いている側面から、ほとんどの時間に気付かないことまで、私たちはそれらを1つずつ分析します。
1。言語レベルのメモリ管理
1.1スコープ
範囲は、JavaScriptプログラミングにおける非常に重要な動作メカニズムです。同期JavaScriptプログラミングで初心者の注目を集めませんが、非同期プログラミングでは、優れた範囲制御スキルがJavaScript開発者にとって必要なスキルになりました。さらに、ScopeはJavaScriptメモリ管理において重要な役割を果たします。
JavaScriptでは、スコープできるステートメントとグローバルスコープを使用して、関数を呼び出すことができます。
例として次のコードに示されているように:
コードコピーは次のとおりです。
var foo = function(){
var local = {};
};
foo();
console.log(local); // =>未定義
var bar = function(){
local = {};
};
バー();
console.log(local); // => {}
ここでは、foo()関数とbar()関数を定義します。彼らの意図は、ローカルという名前の変数を定義することです。しかし、最終結果は完全に異なります。
FOO()関数では、VARステートメントを使用して、ローカル変数が定義されていることを宣言します。スコープは関数本文内に形成されるため、この変数はスコープで定義されます。さらに、FOO()関数にはスコープ拡張処理がないため、関数が実行された後、ローカル変数も破壊されます。ただし、この変数に外側のスコープでアクセスすることはできません。
bar()関数では、ローカル変数はvarステートメントを使用して宣言されるのではなく、ローカルをグローバル変数として直接定義します。したがって、この変数は外側のスコープでアクセスできます。
コードコピーは次のとおりです。
local = {};
//ここでの定義は同等です
Global.local = {};
1.2スコープチェーン
JavaScriptプログラミングでは、スコープチェーンの典型的な表現であるマルチレイヤー機能ネスティングシナリオに間違いなく遭遇します。
次のコードに示されているように:
コードコピーは次のとおりです。
function foo(){
var val = 'hello';
function bar(){
function baz(){
global.val = 'world;'
}
baz();
console.log(val); // =>こんにちは
}
バー();
}
foo();
範囲に関する以前の説明に基づいて、ここのコードに示されている結果は世界であると思われるかもしれませんが、実際の結果はこんにちはです。多くの初心者はここで混乱し始めますので、このコードの仕組みを見てみましょう。
JavaScript以来、変数識別子の検索は現在のスコープから始まり、グローバルスコープまで外見を見ます。したがって、JavaScriptコードの変数へのアクセスは外側にのみ実行できますが、逆ではありません。
BAZ()関数の実行は、グローバル範囲でグローバル変数VALを定義します。 bar()関数では、識別子valにアクセスする場合、内側から外側まで検索する原則は次のとおりです。バー関数の範囲にない場合、前のレイヤー、つまりfoo()関数の範囲になります。
ただし、すべての人を混乱させるための鍵は次のとおりです。この識別子アクセスは、FOO()関数の範囲に一致する変数を見つけ、外向きを見続けることはないため、BAZ()関数で定義されたグローバル変数VALは、この変数アクセスに効果がありません。
1.3閉鎖
JavaScriptの識別子検索は、裏返しの原則に従うことを知っています。ただし、ビジネスロジックの複雑さにより、単一の配信順序は、増加する新しいニーズを満たすことにはほど遠いものです。
次のコードを見てみましょう。
コードコピーは次のとおりです。
function foo(){
var local = 'hello';
return function(){
ローカルを返します。
};
}
var bar = foo();
console.log(bar()); // =>こんにちは
ここに示すように、外側の範囲が内部範囲にアクセスできるようにするテクノロジーは閉鎖です。高次関数の適用のおかげで、FOO()関数の範囲は「拡張」されます。
FOO()関数は、FOO()関数の範囲内に存在する匿名関数を返します。そのため、FOO()関数の範囲内でローカル変数にアクセスし、その参照を保存できます。この関数はローカル変数を直接返すため、bar()関数を外側スコープで直接実行してローカル変数を取得できます。
閉鎖はJavaScriptの高レベルの機能であり、それらを使用して、さまざまなニーズを満たすためにますます複雑な効果を達成できます。ただし、内部変数参照を持つ関数は関数から持ち出されるため、内部変数へのすべての参照がキャンセルされるまで、このスコープの変数は機能しても破壊されないことに注意してください。したがって、閉鎖を適用すると、メモリが容易になりません。
2。JavaScriptのメモリリサイクルメカニズム
ここでは、Chromeとnode.jsが使用し、Googleによって起動し、JavaScriptのメモリリサイクルメカニズムを簡単に導入する例としてGoogleによって起動します。より詳細なコンテンツについては、私の親友であるパークリンの本「詳細で理解しやすいNode.js」を学習用に購入できます。これは、「メモリコントロール」という章で非常に詳細な紹介です。
V8では、すべてのJavaScriptオブジェクトが「ヒープ」を介して割り当てられます。
コード内の値を宣言して割り当てると、V8はこの変数の一部をヒープメモリに割り当てます。要求されたメモリがこの変数を保存するのに不十分である場合、V8はヒープサイズがV8のメモリ限界に達するまでメモリに引き続き適用されます。デフォルトでは、V8のヒープメモリの上限は、64ビットシステムで1464MB、32ビットシステムでは732MBで、約1.4GBおよび0.7GBです。
さらに、V8は、世代のヒープメモリでJavaScriptオブジェクトを管理します:新世代と旧世代。新世代は、一時的な変数、文字列など、短いサバイバルサイクルを持つJavaScriptオブジェクトです。一方、古い世代は、メインコントローラー、サーバーオブジェクトなど、複数のガベージコレクションの後、長いサバイバルサイクルを持つオブジェクトです。
ゴミリサイクルアルゴリズムは常にプログラミング言語の開発の重要な部分であり、V8で使用されるゴミリサイクルアルゴリズムは主に次のとおりです。
1。スカバンゲアルゴリズム:メモリスペース管理は、主に新世代のメモリスペースで使用されるコピーを通じて実行されます。
2.マークスイープアルゴリズムとマークコンパクトアルゴリズム:ヒープメモリは、主に旧世代のオブジェクトの検査とリサイクルに使用されるマーキングによってソートおよびリサイクルされます。
PS:関連する本、ドキュメント、ソースコードを読むことで、より詳細なV8ガベージコレクションの実装を学ぶことができます。
JavaScriptエンジンがどのようなオブジェクトをリサイクルするかを見てみましょう。
2.1スコープと参照
初心者はしばしば、関数が実行されると、関数内で宣言されたオブジェクトが破壊されると誤って信じています。しかし、実際、この理解は厳格で包括的ではなく、混乱するのは簡単です。
参照はJavaScriptプログラミングの非常に重要なメカニズムですが、奇妙なことに、ほとんどの開発者はそれに注意を払ったり、理解したりしません。参照とは、抽象的な関係「オブジェクトへのコードアクセス」を指します。 C/C ++ポインターに多少似ていますが、同じものではありません。参照は、Garbage CollectionのJavaScriptエンジンの最も重要なメカニズムでもあります。
次のコードは例です。
コードコピーは次のとおりです。
// ......
var val = 'hello world';
function foo(){
return function(){
valを返します。
};
}
global.bar = foo();
// ......
このコードを読んだ後、コードのこの部分が実行された後もどのオブジェクトが生き残っているかを知ることができますか?
関連する原則によれば、このコードでリサイクルされてリリースされていないオブジェクトには、valとbar()が含まれます。何が正確に彼らをリサイクルできないのですか?
JavaScriptエンジンはガベージコレクションをどのように実行しますか?上記のごみ収集アルゴリズムは、リサイクル中にのみ使用されます。では、どのオブジェクトをリサイクルできるか、どのオブジェクトが生き残る必要があるかをどのようにして知っていますか?答えは、JavaScriptオブジェクトへの参照です。
JavaScriptコードでは、変数名を何もせずに単一の行として書き留めるだけでも、JavaScriptエンジンは、これがオブジェクトへのアクセス動作であり、オブジェクトへの参照があると考えます。ガベージコレクションの動作がプログラムロジックの動作に影響を与えないようにするために、JavaScriptエンジンは使用中のオブジェクトをリサイクルしてはなりません。そうしないと、乱雑になります。したがって、オブジェクトが使用されているかどうかを判断するための基準は、オブジェクトへの参照がまだあるかどうかです。しかし、実際には、JavaScriptの参照を転送できるため、これは妥協です。したがって、グローバルな範囲にいくつかの参照がもたらされる可能性がありますが、実際には、ビジネスロジックにアクセスする必要はなく、リサイクルする必要はありませんが、JavaScriptエンジンはまだプログラムに必要だと確信しています。
正しい姿勢で変数と参照を使用する方法は、言語レベルからJavaScriptを最適化するための鍵です。
3。JavaScriptを最適化します
ついにポイントに到達しました。忍耐でこれを見てくれてありがとう。上記の多くの紹介の後、JavaScriptのメモリ管理メカニズムをよく理解していると思います。そうすれば、次のスキルにより気分が良くなります。
3.1関数をうまく利用します
優れたJavaScriptプロジェクトを読む習慣がある場合は、フロントエンドJavaScriptコードを開発するときに、多くの大物が匿名関数を使用してコードの最も外側のレイヤーにラップすることがよくあります。
コードコピーは次のとおりです。
(関数() {
//メインビジネスコード
})();
いくつかはさらに高度です:
コードコピーは次のとおりです。
;(function(win、doc、$、undefined){
//メインビジネスコード
})(window、document、jquery);
requirejs、seajs、ozjsなどのフロントエンドモジュラーロードソリューションでさえ、すべて同様のフォームを採用しています。
コードコピーは次のとおりです。
// requirejs
定義(['jQuery']、function($){
//メインビジネスコード
});
// seajs
define( 'module'、['dep'、 'Underscore']、function($、_){
//メインビジネスコード
});
Node.jsのオープンソースプロジェクトの多くのコードがこのように処理されていないと言うと、あなたは間違っています。実際にコードを実行する前に、node.jsは各.jsファイルを次のフォームに巻き付けます。
コードコピーは次のとおりです。
(function(exports、require、module、__dirname、__filename){
//メインビジネスコード
});
これを行うことの利点は何ですか?私たちは皆、記事の冒頭で、JavaScriptにはステートメントとグローバルな範囲がある機能が範囲にあると述べました。また、グローバルスコープで定義されているオブジェクトは、プロセスが終了するまで生き残る可能性があることもわかっています。それが大きなオブジェクトである場合、それは厄介です。たとえば、一部の人々はJavaScriptでテンプレートをレンダリングするのが好きです。
コードコピーは次のとおりです。
<?php
$ db = mysqli_connect(server、user、password、 'myApp');
$ topics = mysqli_query($ db、 "select * from topics;");
?>
<!doctype html>
<html lang = "en">
<head>
<メタcharset = "utf-8">
<Title>あなたはサルに招待された面白い男ですか? </title>
</head>
<body>
<ul id = "Topics"> </ul>
<script type = "text/tmpl" id = "topic-tmpl">
<li>
<h1> <%= title%> </h1>
<p> <%= content%> </p>
</li>
</script>
<script type = "text/javascript">
var data = <?php echo json_encode($ topics); ?>;
var topictmpl = document.queryselector( '#topic-tmpl')。innerhtml;
var render = function(tmlp、view){
var compiled = tmlp
.replace(// n/g、 '// n')
.replace(/<%=([/s/s]+?)%>/g、function(match、code){
return '" + escase(' + code + ') +"';
});
コンパイル= [
'var res = "";'、
'with(view || {}){'、
'res = "' +コンパイル + '";'、
'}'、
'Return Res;'
] .join( '/n');
var fn = new Function( 'View'、コンパイル);
fn(view);
};
var topics = document.queryselector( '#topics');
関数init()
data.foreach(function(topic){
topics.innerhtml += render(topictmpl、topic);
});
}
init();
</script>
</body>
</html>
この種のコードは、初心者の作品でしばしば見ることができます。ここでの問題は何ですか?データベースから取得したデータの量が非常に大きい場合、フロントエンドがテンプレートレンダリングを完了した後、データ変数はアイドル状態になります。ただし、この変数はグローバルスコープで定義されているため、JavaScriptエンジンはリサイクルして破壊しません。これは、ページが閉じられるまで、古い世代のヒープメモリに存在し続けます。
しかし、いくつかの非常に簡単な変更を行い、論理コードの外側に関数のレイヤーをラップすると、効果は非常に異なります。 UIレンダリングが完了した後、データへのコードの参照もキャンセルされます。最も外側の関数が実行されると、JavaScriptエンジンがその中のオブジェクトを確認し始め、データをリサイクルできます。
3.2グローバル変数を定義しないでください
変数がグローバル範囲で定義されている場合、JavaScriptエンジンはデフォルトでリサイクルして破壊しません。これは、ページが閉じられるまで、古い世代のヒープメモリに存在し続けます。
その後、私たちは常に原則に従いました。グローバル変数を使用しないでください。グローバル変数は実際に非常に簡単に開発できますが、グローバル変数によって引き起こされる問題は、それがもたらす利便性よりもはるかに深刻です。
変数をリサイクルする可能性が低くなります。
1.複数の人が協力すると混乱が簡単に発生します。
2。スコープチェーンで干渉するのは簡単です。
3。上記のラッピング関数と組み合わせて、ラッピング関数を通じて「グローバル変数」を処理することもできます。
3.3手動での非参照変数
ビジネスコードで変数が必要ない場合、変数を手動でリサイクルするようにリサイクルすることができます。
コードコピーは次のとおりです。
var data = { / *いくつかのビッグデータ * /};
//何とか何とか何とか
data = null;
3.4コールバックをうまく利用します
内部変数アクセスに閉鎖を使用することに加えて、ビジネス処理に非常に人気のあるコールバック関数を使用することもできます。
コードコピーは次のとおりです。
関数getData(コールバック){
var data = 'いくつかのビッグデータ';
コールバック(null、data);
}
getData(function(err、data){
console.log(data);
コールバック関数は、継続パッシングスタイル(CPS)のテクノロジーです。このスタイルのプログラミングは、関数のビジネスフォーカスを戻り値からコールバック関数に転送します。そして、それは閉鎖よりも多くの利点があります:
1.渡されたパラメーターが基本タイプ(文字列、数値など)である場合、コールバック関数に渡される正式なパラメーターがコピーされ、ビジネスコードを使用した後にリサイクルが簡単になります。
2。同期リクエストの完了に加えて、コールバックを介して、現在非常に人気のある執筆スタイルである非同期プログラミングでそれらを使用することもできます。
3.コールバック関数自体は通常、一時的な匿名関数です。要求関数が実行されると、コールバック関数自体への参照がキャンセルされ、リサイクルされます。
3.5良好な閉鎖管理
当社のビジネスニーズ(循環イベントのバインディング、プライベート属性、引数を含むコールバックなど)は閉鎖を使用する必要がある場合は、詳細に注意してください。
ループバインディングイベントは、JavaScriptの閉鎖を開始するための強制コースであると言えます。シナリオを想定しましょう。6つのイベントに対応する6つのボタンがあります。ユーザーがボタンをクリックすると、対応するイベントが指定された場所で出力されます。
コードコピーは次のとおりです。
var btns = document.queryselectorall( '。btn'); // 6つの要素
var output = document.queryselector( '#output');
var events = [1、2、3、4、5、6];
//ケース1
for(var i = 0; i <btns.length; i ++){
btns [i] .onclick = function(evt){
output.innertext + = 'clicked' + events [i];
};
}
//ケース2
for(var i = 0; i <btns.length; i ++){
btns [i] .onclick =(function(index){
return function(evt){
output.innertext + = 'clicked' + events [index];
};
})(私);
}
//ケース3
for(var i = 0; i <btns.length; i ++){
btns [i] .onclick =(function(event){
return function(evt){
output.innertext + = 'clicked' + event;
};
})(events [i]);
}
ここでの最初のソリューションは、明らかに典型的なループ結合イベントエラーです。ここでは詳しく説明しません。ネチズンへの私の答えを詳細に参照できます。 2番目と3番目のソリューションの違いは、閉鎖で渡されたパラメーターにあります。
2番目のスキームで渡されたパラメーターは現在のループの添え字であり、後者は対応するイベントオブジェクトに直接渡されます。実際、後者は大量のデータアプリケーションにより適しています。これは、JavaScriptの関数プログラミングでは、関数呼び出しで渡されるパラメーターが基本的なタイプのオブジェクトであるため、関数本体で得られる正式なパラメーターはコピー値になるため、この値は関数本体の範囲内のローカル変数として定義されます。イベントバインディングが完了した後、イベント変数を手動で繰り返し回避して、外側のスコープでのメモリ使用量を減らすことができます。さらに、要素が削除されると、対応するイベントリスニング機能、イベントオブジェクト、および閉鎖関数も破壊され、リサイクルされます。
3.6メモリはキャッシュではありません
ビジネス開発におけるキャッシュの役割は重要な役割を果たし、時空のリソースの負担を減らすことができます。ただし、メモリを簡単にキャッシュとして使用しないでください。記憶は、あらゆるプログラム開発のための土地のあらゆるインチのものです。それが非常に重要なリソースではない場合は、それをメモリに直接入れないでください。または、有効期限キャッシュを自動的に破壊するために有効期限メカニズムを策定しないでください。
4. JavaScriptのメモリ使用法を確認します
毎日の開発では、いくつかのツールを使用して、JavaScriptのメモリ使用を分析およびトラブルシューティングすることもできます。
4.1 Blink/WebKitブラウザ
Blink/WebKitブラウザ(Chrome、Safari、Operaなど)では、開発者ツールのプロファイルツールを使用して、プログラムでメモリチェックを実行できます。
4.2 node.jsのメモリチェック
node.jsでは、メモリチェックにノードヒープダンプとノードメムウォッチモジュールを使用できます。
コードコピーは次のとおりです。
var heapdump = require( 'heapdump');
var fs = require( 'fs');
var path = require( 'path');
fs.writefilesync(path.join(__ dirname、 'app.pid')、process.pid);
// ...
コードコピーは次のとおりです。<span style = "font-family:georgia、 'times new roman'、 'bitstream charter'、 'bitstream charter'、times、serif; font-size:14px; line-height:1.5em;"> node-heapdumpをビジネスコードに導入した後、sigusr2の信号をNode.jsプロセスに送信する必要があります。ヒープメモリのスナップショット。 </span>
コードを次のようにコピーします:$ kill -USR2(cat app.pid)
このようにして、ファイルディレクトリにHeapDump- <sec>。ブラウザの開発者ツールのプロファイルツールを使用して開いて確認できます。
5。概要
記事はまたすぐに来ます。この共有は、主に次のコンテンツを示しています。
1. JavaScriptは、言語レベルでのメモリ使用法と密接に関連しています。
2。JavaScriptのメモリ管理とリサイクルメカニズム。
3.生成されたJavaScriptをより拡張してエネルギッシュにできるように、メモリをより効率的に使用する方法。
4.メモリの問題に遭遇したときのメモリチェックを実行する方法。
この記事を学ぶことで、より優れたJavaScriptコードを作成して、母親が安心し、上司が安心していると感じさせることを願っています。