javascriptの小さい特性——関数式プログラミング

6935 ワード

まず何かを話します.「関数式プログラミング」はウィキペディアの概念を借ります.
関数プログラミングは、コンピュータ演算を関数として計算します.関数式プログラミングのポイントは、命令式プログラミングのようにステータスマシンの実現を強調するのではなく、関数の定義です.
つまり、関数式プログラミングはプログラム入力で実行される操作だけを説明しています.「どうすればいいですか?」ではなく、「どうすればいいですか?」をキャッチすることに重点を置いています.一つの関数がどのような結果を返すかを知るだけで、結果をさらなる演算に使います.
誤解しやすい概念があります.「関数式プログラミングは関数の山です.」これは間違いです.言語サポート関数ではなく、この言語を「関数言語」といいます.関数言語の「関数(funct ion)」は、呼び出すことができるだけでなく、他のいくつかの性質を持っています.
関数は演算元です.
関数内にデータを保存する関数内の演算は、関数外に副作用がないです.
 
JavaScriptに戻ると、JavaScriptは関数式プログラミング言語ではなく、関数式プログラミングの特性を参照して実装されているというだけで、「半関数プログラミング言語」です.以下はJavaScriptの関数プログラムのいくつかの特性を紹介します.
 
一、関数(Function)は一等公民です.
FunctionはJavaScriptの中で最も基本的なモジュールであり、自身は特殊なオブジェクトであり、最上階のオブジェクトであり、他のオブジェクトに依存せずに独立して存在することができ、対象に向けた言語では、Functは対象の一部に依存する.JavaScriptの中ではすべて対象です.Functionももちろん対象です.あらゆる角度からFunctionに伝わる値です.Function自体も例外ではありません.
このように何かメリットがありますか?並べ替えの例を挙げます.
//Array   sort  ,        
varmyarray = [2,5,7,3];
varbyAsc = function(x,y) { returnx-y; };
varbyDesc = function(x,y){returny-x; };
myarray.sort(byDesc);
alert(myarray);   //7,5,3,2
myarray.sort(byAsc);
alert(myarray);   //2,3,5,7
外部に公開されているFunctionを定義する必要は全くありません.直接に匿名関数を使用します.
//         
dateArr.sort(function(x,y) {
    returnx.date – y.date;
});
 
二、高次関数
高次関数とは、関数のより抽象的なもので、上記のsortはJSエンジン自体が提供する高次関数である.ソトから入ってきた比較関数(byAcs,byDesc)は、事前の仮定は一切なく、ソトは順序付け方法全体に対する二次抽象的であり、したがって「高次」関数と呼ばれる.
次に高次関数の例を挙げます.配列要素は遍歴します.
Array.prototype.each = function(fun){
    varret = [], len = this.length, i;
    for(i = 0; i < len ; i++){
        ret.push(fun(this[i],i));
    }
    returnret;
}
alert([1,2,3].each(function(x){returnx+1;}));  //[2,3,4]
alert([1,2,3].each(function(x){returnx*2;}));  //[2,4,6]
コードを再利用すると何かのメリットがありますか?
 
三、閉包する
この特性は初心者にはよく分からないかもしれません.詳しくは前に書いた「JavaScriptのクローズドとスコープチェーン」を見てください.クローズドの例を挙げます.
//       
functioncounter(){
    varn = 0;
    returnfunction(){return++n; }
}
//       
varcount1 = counter();
count1(); //1
count1(); //2
count1(); //3
//     
varcount2 = counter();
count2(); //1
count2(); //2
クローズドの最大の特性は、変数を伝達することなしに、内部層から直接に外部層の環境にアクセスできることであり、これは多重ネスト下の関数式プログラムに大きな利便性をもたらします.
クローズドがあれば、関数内でデータを保存できるということですが、何かメリットがありますか?
 
四、関数コリック化(Currying)
コリ化とは?詳しくはウィキペディアをご覧ください.
コンピュータ科学では、コリック化は、複数のパラメータを受け取る関数を単一のパラメータ(最初の関数の最初のパラメータ)を受け入れる関数に変換し、残りのパラメータを受け入れて結果を返す新しい関数の技術です.
「クローズド」という利器があったら、「コリ化」というクールなプログラミング体験を楽しむことができます.
//    Ajax         
 
//  DOM      html
functionupdate(id){
    returnfunction (data){
        $("div#"+id).html(data.text);
    }
}
 
//Ajax    
functionrefresh(url, callback){
    $.ajax({
        type:"get",
     url:url,
        dataType:"json",
     success: function(data){
            callback(data);
    });
}
 
//      
refresh("friends.php", update("friendsDiv"));
refresh("newfeeds.php", update("feedsDiv"));
上のudate関数のプロトタイプは元々udate(id,data)で、二つのパラメータを受信します.カリー化後はまずIDを受け取り、リフレッシュエリアはどこかを確定し、残りのパラメータを受信する関数の一つを返してrefresh関数のcalbackとし、ajaxがdataに戻った後はcalbackに入り、ページを更新します.なぜrefreshを直接refresh(id,url)にしないのかという質問があるかもしれません.これはudateの方法が多いかもしれません.udate 1、udate 2といった一連の方法があるかもしれません.戻り結果に対する処理は全部違っています.このような「コリ化」によってrefshは高次の抽象を得られ、より良い再利用ができます.
 
五、Memoizationの再帰的最適化
再帰はシナリオのスピードを遅くする大敵の一つで、多すぎる再帰はブラウザーをますます遅くなります.死んでしまうか、訳がわからないまま自動的に退出します.クローズドがあったら、私たちはmemoization技術によって関数の中の多すぎる再帰的な呼び出しを代替して、JavaScriptの効率を向上させます.
Memoizationでは、関数の中で以前の運転結果をキャッシュします.そうすると、計算済みの結果を再計算する必要はありません.
よく知っている例を挙げます.フィボナッチの数列(ウサギ問題):
ウサギは生まれて二ヶ月後に繁殖能力があります.一対のウサギは毎月一匹のウサギを産みます.すべてのウサギが死なないなら、一年後にどれぐらいのウサギを繁殖できますか?
これはうるさい小学校の問題です.答えはみんなとよく分かりません.n月のウサギの数はF(n)、F(0)=0、F(1)=1、F(n)=F(n)=F(n−1)+F(n>=2、n〓N*)です.
javascriptコードを作成すると以下の通りです.
functionfibonacci(n){
    returnn<2 ? n : fibonacci(n-1) + fibonacci(n-2);
};
うん、見た目は小柄で精悍で、とてもいいです.でも、もしfibonacci(100)を試してみたら、あなたのブラウザは東南の枝に掛かっています.
どうしてですか?私達はそれにカウンタを入れたら分かります.(時間を節約してデモンストレーションしません.)fibonacci(10)を呼び出した時、自分で176回、合計177回、11時287回、12時465回…fibonacci(20)を呼んだ時2191回…これもお父さんに穴をあけました.
実際にはfabonacciが自身を呼び出した時、多くの計算結果がすでに出ています.無駄な計算を繰り返して、最終的にコールスタックが溢れました.どうしたらいいですか
覚えていますか?覚えていますか?今は関数内でオブジェクトを保存できます.これが私が言いたいMemoizationの方法です.上のコード:
//      ,     ,     
functionmemoizer(fun, cache) {
    cache = cache || {};
    //        ,       fun   
    varshell = function(arg) {
        //       ,          
        //           
        if(! (arg incache)) {
            //   fun   shell      
            cache[arg] = fun(shell, arg);
        }
        returncache[arg];
    };
    returnshell;
}
これは一般的な方法です.この時にfabonacciを書き換えることができます.
varfactorial = memoizer(function(shell, n){
    returnn<2 ? n : shell(n-1) + shell(n-2);
});
今回、fabonacci(100)は圧力がないですね.35424817926200万円です.101回だけ呼び出しました.  ,自負心がわく
同じ理屈で、どの階乗演算などもMemoizationの方法で解決できます.so easuy! 
六、関数コードスタイル
いくつかの言語では、連続演算は良好なプログラミング習慣ではないと考えられています.私たちは結果値を計算して、先に中間変数に入れて、中間変数を持って演算に参加します.しかし、関数式の言語では、連続演算が推奨される方法である(原因は不明で、個人は純粋な関数式言語に触れたことがなく、理解が深くない).
JavaScrr iptにおいて、一般的な場合は連続して与えられる値である.
vara = b = c = d = 100;
また、私たちがよく使う「短絡」の条件は、「??」と「変数のデフォルト値を提供するために用いられます.&&&"はundefinedから値を抜き出して異常をなくすことができます.」
例えば、三元表現:
varobjType = getFromInput();
varcls = ((objType == 'String') ? String :
    (objType == 'Array') ? Array :
    (objType == 'Number') ? Number :
    (objType == 'Boolean') ? Boolean :
    (objType == 'RegExp') ? RegExp :
    Object
);
varobj = newcls();
if/else、switch、さらには「多态」で上记の方法を书き直すなら、绝対に长くなります.このコードのスタイルは分かりやすいと思います.if/elseより悪いとは限りません.
もちろん、JS攻城師が一番好きなチェーンコール、例えばjQueryのDOM操作もあります.
//  jQuery   ~
$('#item').width('100px')
        .height('100px').
        .css('padding','20px')
        .click(function(){
            alert('hello');
         });
実は原理は関数の最後のreturn thisで、すぐ前のコンテキスト環境に続いて関数を引き続き呼び出すことができて、このようにするのはとてもさわやかでしょう!
 
七、関数内の演算は関数外に副作用がない
実は、これはJavaScr iptの特性ではありません.これは関数式言語の達成すべき特性です.JavaScar iptでは、この特性は開発者のプログラミング習慣によってしか保証できません.
関数外に副作用がないということは、
関数は、変数パラメータではなく、入口パラメータを用いて演算します.
演算中は、関数の外部の他のデータの値(例えば、すべての変数)は変更されません.演算が終了したら、関数を介して外部システムに値を転送します.これは何のメリットがありますか?
関数がその作用領域以外の量を修正し、他の関数によって使用されたことがありません.これは関数シークの結果が戻り値だけであり、その戻り値に影響するのは関数のパラメータだけです.
もし関数プログラムが期待通りに実行されないなら、デバッグは簡単です.関数プログラムのバグは実行前と無関係のコードパスに依存しないので、あなたが遭遇した問題は常に再現できます.ユニットテストでは、関数の呼び出し順序を考慮せずにパラメータを気にするだけで、外部状態を慎重に設定する必要はありません.すべてのやるべきことは、限界を表すパラメータを伝えることです.プログラムの各関数がユニットテストに合格したら、このソフトの品質にかなり自信があります.
命令式プログラミングはこのように楽観的にはできません.JavaまたはC++の中で関数の戻り値だけをチェックするのはまだ足りません.この関数が修正した可能性がある外部状態を検証しなければなりません.
このような特性は実はプログラムの「高凝集、低結合」の一種の具現であり、実際の開発にはできるだけ従うべきである.
私のブログ:http://technicolor.flycoder.org/articles/793.html