純粋な機能
次の2つの要件を満たす関数は、純粋と呼ばれます。
- 同じ引数で呼び出されると、常に同じ結果が返されます。
- 関数の実行時に副作用は発生しません。
例を考えてみましょう:
function circleArea(radius){
return radius * radius * 3.14
}
この関数に同じ値が渡されると
radius
、常に同じ結果が返されます。同時に、関数の実行中、関数の外側には何も変更されません。つまり、副作用はありません。これはすべて、これが純粋な機能であることを意味します。
別の例を次に示します。
let counter = (function(){
let initValue = 0
return function(){
initValue++;
return initValue
}
})()
ブラウザコンソールでこの機能を試してみましょう。
ブラウザコンソールでの関数のテスト
ご覧のとおり
counter
、カウンタを実装する関数は、呼び出されるたびに異なる結果を返します。したがって、純粋とは言えません。
そしてここに別の例があります:
let femaleCounter = 0;
let maleCounter = 0;
function isMale(user){
if(user.sex = 'man'){
maleCounter++;
return true
}
return false
}
ここに示されているのは
isMale
、同じ引数が渡されると、常に同じ結果を返す関数です。しかし、それは副作用があります。つまり、グローバル変数の変更について話しているのですmaleCounter
。結果として、この関数は純粋とは言えません。
▍なぜ純粋な機能が必要なのですか?
通常の関数と純粋な関数の間に線を引くのはなぜですか?重要なのは、純粋な機能には多くの長所があるということです。それらを使用すると、コードの品質を向上させることができます。純粋な関数の使用が私たちに何を与えるかについて話しましょう。
1.純粋な関数のコードは、通常の関数のコードよりも明確で、読みやすくなっています。
それぞれの純粋な機能は、特定のタスクを対象としています。同じ入力で呼び出されると、常に同じ結果が返されます。これにより、コードの読みやすさが大幅に向上し、文書化が容易になります。
2.純粋な関数は、コードをコンパイルするときに最適化に役立ちます
次のようなコードがあるとします。
for (int i = 0; i < 1000; i++){
console.log(fun(10));
}
fun
-これが純粋ではない関数である
場合、このコードの実行中に、この関数をfun(10)
1000回呼び出す必要があります。
そして、それ
fun
が純粋な関数である場合、コンパイラーはコードを最適化できます。次のようになります。
let result = fun(10)
for (int i = 0; i < 1000; i++){
console.log(result);
}
3.純粋な関数はテストが簡単です
純粋な機能テストは、状況依存であってはなりません。純粋な関数のユニットテストを作成するとき、それらは単にいくつかの入力値をそのような関数に渡し、特定の要件に対して何が返されるかをチェックします。
これが簡単な例です。純粋な関数は、引数として数値の配列を取り、その配列の各要素に1を追加して、新しい配列を返します。簡略表示は次のとおりです。
const incrementNumbers = function(numbers){
// ...
}
このような関数をテストするには、次のようなユニットテストを作成するだけで十分です。
let list = [1, 2, 3, 4, 5];
assert.equals(incrementNumbers(list), [2, 3, 4, 5, 6])
関数が純粋でない場合、それをテストするには、その動作に影響を与える可能性のある多くの外部要因を考慮する必要があります。結果として、そのような機能のテストは、純粋な機能のテストよりも困難になります。
高次関数。
高次関数は、次の機能の少なくとも1つを備えた関数です。
- 他の関数を引数として受け入れることができます。
- 作業の結果として関数を返すことができます。
高次の関数を使用すると、コードの柔軟性が高まり、よりコンパクトで効率的なプログラムを作成できます。
整数の配列があるとしましょう。同じ長さの新しい配列を作成する必要がありますが、その各要素は、元の配列の対応する要素に2を掛けた結果を表します。
高次関数の機能を使用しない場合、この問題の解決策は次のようになります。
const arr1 = [1, 2, 3];
const arr2 = [];
for (let i = 0; i < arr1.length; i++) {
arr2.push(arr1[i] * 2);
}
この問題について考えると
Array
、JavaScriptの型オブジェクトにはメソッドがあることがわかりますmap()
。このメソッドはと呼ばれmap(callback)
ます。呼び出された配列の要素で満たされた新しい配列を作成し、渡された関数で処理しcallback
ます。
この方法を使用したこの問題の解決策は次のようになり
map()
ます。
const arr1 = [1, 2, 3];
const arr2 = arr1.map(function(item) {
return item * 2;
});
console.log(arr2);
メソッド
map()
は、高次関数の例です。
高次関数を正しく使用すると、コードの品質が向上します。この資料の次のセクションでは、そのような機能に複数回戻ります。
キャッシング機能の結果
次のような純粋な関数があるとします。
function computed(str) {
// ,
console.log('2000s have passed')
// ,
return 'a result'
}
コードのパフォーマンスを向上させるために、関数で実行された計算の結果をキャッシュすることに頼っても害はありません。すでに呼び出されているのと同じパラメーターを使用してこのような関数を呼び出す場合、同じ計算を再度実行する必要はありません。代わりに、以前にキャッシュに保存されていた結果がすぐに返されます。
関数にキャッシュを装備する方法は?これを行うには、ターゲット関数のラッパーとして使用できる特別な関数を作成できます。この特別な関数に名前を付け
cached
ます。この関数は、目的関数を引数として取り、新しい関数を返します。関数でcached
は、通常のオブジェクト(Object
)を使用するか、データ構造であるオブジェクトを使用して、関数をラップした関数の呼び出し結果のキャッシュを整理できます。Map
..。
これは、関数コードがどのように見えるかです
cached
:
function cached(fn){
// , fn.
const cache = Object.create(null);
// fn, .
return function cachedFn (str) {
// - fn
if ( !cache[str] ) {
let result = fn(str);
// , fn,
cache[str] = result;
}
return cache[str]
}
}
これは、ブラウザコンソールでこの機能を試した結果です。
結果がキャッシュされる関数の実験
怠惰な機能
関数本体には、通常、いくつかの条件をチェックするためのいくつかの指示があります。それらに対応する条件を一度だけチェックする必要がある場合があります。関数が呼び出されるたびにそれらをチェックする意味はありません。
このような状況では、最初に実行された後にこれらの命令を「削除」することにより、関数のパフォーマンスを向上させることができます。その結果、関数は、後続の呼び出し中に、不要になったチェックを実行する必要がないことがわかります。これが「レイジー」機能になります。その関数が最初に呼び出されたときに作成されたオブジェクトを常に返す
関数を作成する必要があるとします。関数が最初に呼び出されたときに正確に作成されたオブジェクトが必要であることに注意してください。 そのコードは次のようになります。
foo
Date
let fooFirstExecutedDate = null;
function foo() {
if ( fooFirstExecutedDate != null) {
return fooFirstExecutedDate;
} else {
fooFirstExecutedDate = new Date()
return fooFirstExecutedDate;
}
}
この関数を呼び出すたびに、条件を確認する必要があります。この状態が非常に難しい場合、そのような関数を呼び出すと、プログラムのパフォーマンスが低下します。ここで、「遅延」関数を作成する手法を使用してコードを最適化できます。
つまり、関数を次のように書き直すことができます。
var foo = function() {
var t = new Date();
foo = function() {
return t;
};
return foo();
}
関数を最初に呼び出した後、元の関数を新しい関数に置き換えます。この新しい関数は、関数が最初に呼び出さ
t
れたときにDate
作成されたオブジェクトによって表される値を返します。その結果、このような関数を呼び出すときに条件をチェックする必要はありません。このアプローチにより、コードのパフォーマンスを向上させることができます。
これは非常に単純な条件付きの例でした。では、現実に近いものを見てみましょう。
イベントハンドラーをDOM要素にアタッチするときは、ソリューションが最新のブラウザーおよびIEと互換性があることを確認するためのチェックを実行する必要があります。
function addEvent (type, el, fn) {
if (window.addEventListener) {
el.addEventListener(type, fn, false);
}
else if(window.attachEvent){
el.attachEvent('on' + type, fn);
}
}
関数を呼び出すたび
addEvent
に、条件がチェックされることがわかります。これは、最初に呼び出されたときに1回だけチェックするのに十分です。この関数を「レイジー」にしましょう。
function addEvent (type, el, fn) {
if (window.addEventListener) {
addEvent = function (type, el, fn) {
el.addEventListener(type, fn, false);
}
} else if(window.attachEvent){
addEvent = function (type, el, fn) {
el.attachEvent('on' + type, fn);
}
}
addEvent(type, el, fn)
}
その結果、1回だけ実行する必要のある関数で特定の条件をチェックすると、「レイジー」関数を作成する手法を適用することで、コードを最適化できると言えます。つまり、最適化とは、最初の条件チェックの後、元の関数が新しい関数に置き換えられ、条件のチェックがなくなるということです。
カレー機能
カレーは、関数のそのような変換であり、適用後、一度に複数の引数を渡す前に呼び出さなければならなかった関数が、必要な引数を一度に1つずつ渡すことによって呼び出すことができる関数に変わります。
言い換えれば、正しく機能するためにいくつかの引数を必要とするカレー関数は、それらの最初のものを受け入れ、2番目の引数を取ることができる関数を返すことができるという事実について話しているのです。次に、この2番目の関数は、3番目の引数を取り、新しい関数を返す新しい関数を返します。これは、必要な数の引数が関数に渡されるまで続きます。
これの用途は何ですか?
- カレーは、同じ引数を何度も渡すことにより、関数を呼び出す必要がある状況を回避するのに役立ちます。
- この手法は、高次の関数を作成するのに役立ちます。イベントの処理に非常に役立ちます。
- カレーのおかげで、特定のアクションを実行するための関数の準備を整理し、そのような関数をコードで便利に再利用できます。
渡された数値を加算する単純な関数について考えてみます。それを呼びましょう
add
。引数として3つのオペランドを取り、それらの合計を返します。
function add(a,b,c){
return a + b + c;
}
このような関数は、必要な数よりも少ない引数を渡すことで呼び出すことができます(ただし、これにより、期待されるものとはまったく異なるものが返されるという事実につながります)。また、作成時に提供されたよりも多くの引数を使用して呼び出すこともできます。このような状況では、「不要な」引数は単に無視されます。同様の機能を試すと、次のようになります。
add(1,2,3) --> 6
add(1,2) --> NaN
add(1,2,3,4) --> 6 // .
そのような機能をカレーする方法は?他の関数をカレーすることを目的とし
た関数のコードは
curry
次のとおりです。
function curry(fn) {
if (fn.length <= 1) return fn;
const generator = (...args) => {
if (fn.length === args.length) {
return fn(...args)
} else {
return (...args2) => {
return generator(...args, ...args2)
}
}
}
return generator
}
これは、ブラウザコンソールでこの機能を試した結果です。
ブラウザコンソールでカレーを試す
機能構成
文字列を入力として受け取り
bitfish
、文字列を返す関数を作成する必要があるとしますHELLO, BITFISH
。
ご覧のとおり、この関数には2つの目的があります。
- 文字列の連結。
- 結果の文字列の文字を大文字に変換します。
このような関数のコードは次のようになります。
let toUpperCase = function(x) { return x.toUpperCase(); };
let hello = function(x) { return 'HELLO, ' + x; };
let greet = function(x){
return hello(toUpperCase(x));
};
それを試してみましょう。
ブラウザコンソールでの機能のテスト
このタスクには、個別の機能として編成された2つのサブタスクが含まれています。その結果、機能コード
greet
は非常に単純です。文字列に対してさらに操作を実行する必要がある場合、関数にgreet
はのような構造が含まれますfn3(fn2(fn1(fn0(x))))
。
問題の解決を単純化して、他の関数を構成する関数を書いてみましょう。それを呼びましょう
compose
。そのコードは次のとおりです。
let compose = function(f,g) {
return function(x) {
return f(g(x));
};
};
これで、関数
greet
を使用して関数を作成できますcompose
。
let greet = compose(hello, toUpperCase);
greet('kevin');
関数
compose
を使用して、既存の2つの関数に基づいて新しい関数を作成することは、それらの関数を左から右に呼び出す関数を作成することを意味します。その結果、読みやすいコンパクトなコードが得られます。
これで、関数
compose
は2つのパラメーターのみを取ります。そして、任意の数のパラメーターを受け入れることができるようにしたいと思います。
よく知られているオープンソースのアンダースコアライブラリでは、任意の数のパラメータを受け入れることができる同様の関数を利用できます。
function compose() {
var args = arguments;
var start = args.length - 1;
return function() {
var i = start;
var result = args[start].apply(this, arguments);
while (i--) result = args[i].call(this, result);
return result;
};
};
関数構成を使用することにより、関数間の論理関係をより理解しやすくし、コードの読みやすさを向上させ、将来の拡張とリファクタリングの基礎を築くことができます。
JavaScriptプロジェクトで関数を操作する特別な方法を使用していますか?