通常のプログラミング言語では、関数のパラメーターは基本型またはオブジェクト参照のみにすることができ、戻り値は基本データ型またはオブジェクト参照のみにすることができます。しかし、JavaScript では、関数は第一級市民であり、パラメーターとして渡したり、戻り値として返すことができます。いわゆる高階関数とは、関数をパラメータとして、または関数を戻り値として受け取ることができる関数です。この 2 つの状況には、実際の開発で多くの応用シナリオがあります。この記事は、私が仕事や勉強で遭遇したいくつかの応用シナリオをまとめたものです。
コールバック関数
コードの再利用は、アプリケーションを評価するための重要な基準の 1 つです。変更されたビジネスロジックを抽出してコールバック関数にカプセル化することで、コードの再利用率を効果的に向上させることができます。たとえば、ES5 で配列に追加された forEach メソッドは配列を走査し、各要素で同じ関数を呼び出します。
array = {}; array.forEach = function(arr, fn){ for (var i = 0, len = arr.length; i < len; i++) { fn(arr[i], i, arr); } }
毎回トラバーサル コードを再度記述する必要がなく、ビジネスの焦点をコールバック関数に集中させます。
部分関数
関数を戻り値として出力する応用例としては、部分関数です。いわゆる部分関数とは、パラメータまたは変数が事前に設定されている別の部分、つまり関数を呼び出す関数を作成する使用方法を指します。とにかく、定義を見ても、これが何のためにあるのかわかりません。まず例を見てみましょう。部分関数の最も典型的な例は型判定です。
JavaScript オブジェクトには、プロトタイプ属性、クラス属性、拡張性という 3 つの属性があります。 (知らない学生は、Rhino の本の 138 ページに戻って読んでください。) class 属性は文字列であり、JavaScript では直接提供されませんが、Object.prototype.toString を使用して間接的に取得できます。この関数は常に次の形式を返します:
[オブジェクトクラス]
それで、一連の isType 関数を書くことができます。
コードは次のとおりです:
isString = function(obj){ return Object.prototype.toString.call(obj) === "[object String]"; } isNumber = function(obj){ return Object.prototype.toString.call(obj) === "[object Number]"; } isArray = function(obj){ return Object.prototype.toString.call(obj) === "[object Array]"; }
これらの関数のコードのほとんどは、現時点では、高階関数が豪華なデビューを果たしています:
isType = function(type) { return function(obj) { return Object.prototype.toString.call(obj) === "[object " + type + "]"; } } isString = isType('String'); isNumber = isType('Number'); isArray = isType('Array');
したがって、いくつかのパラメータを指定して新しいカスタマイズされた関数を返す形式は部分関数です。
カレー作り
カリー化は部分評価とも呼ばれます。カリー化関数は、最初にいくつかのパラメータを受け入れます。これらのパラメータを受け入れた後、関数はすぐには評価されませんが、渡されたばかりのパラメータは関数によって形成されたクロージャに保存されます。関数が実際に評価されるときは、渡されたすべてのパラメーターが一度に評価に使用されます。
var currying = function(fn) { var args = []; return function() { if (arguments.length === 0) { return fn.applay(this, args); } else { args = args.concat(arguments); return arguments.callee; } } }
例として、1 か月の毎日の出費を計算するとします。
var currying = function(fn) { debugger; var args = []; return function() { if (arguments.length === 0) { return fn.apply(this, args); } else { Array.prototype.push.apply(args, arguments); return arguments.callee; } } } cost = function(){ var sum = 0; for (var i = 0, len = arguments.length; i < len; i++) { sum += arguments[i]; } return sum; } var cost = currying(cost); cost(100); cost(200); alert(cost())
イベントスロットル
シナリオによっては、特定のイベントが繰り返しトリガーされる場合がありますが、イベント処理関数を毎回実行する必要はありません。たとえば、複雑な論理計算は window.resize イベントで実行されます。ユーザーがブラウザのサイズを頻繁に変更する場合、複雑な計算はパフォーマンスに重大な影響を与える可能性があり、サイズ変更が発生するたびにこれらの論理計算をトリガーする必要はありません。必要な計算は数回だけで済みます。現時点では、期間に基づいて一部のイベント リクエストを無視する必要があります。次のスロットル関数を見てください:
function throttle(fn, interval) { var doing = false; return function() { if (doing) { return; } doing = true; fn.apply(this, arguments); setTimeout(function() { doing = false; }, interval); } } window.onresize = throttle(function(){ console.log('execute'); }, 500);
関数の実行時間を制御することで、関数の実行数と機能要件の完璧なバランスを実現できます。もう 1 つのイベントは、mousemove です。このイベントを DOM 要素にバインドすると、マウスが要素上に移動するとイベントが繰り返しトリガーされます。
イベントは終了しました
頻繁にトリガーできる一部のイベントでは、イベント終了後に一連の操作を実行したい場合があります。現時点では、高階関数を使用して次の処理を行うことができます:
function debounce(fn, interval) { var timer = null; function delay() { var target = this; var args = arguments; return setTimeout(function(){ fn.apply(target, args); }, interval); } return function() { if (timer) { clearTimeout(timer); } timer = delay.apply(this, arguments); } }; window.onresize = throttle(function(){ console.log('resize end'); }, 500);
このプロセス中にイベントがトリガーされた場合は、最後のイベント ハンドルをクリアし、実行時間を再バインドします。
参照:
《ノードの徹底解説》
「JavaScript の設計パターンと開発実践」