在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 函數(shù)式的 Javascript
面向?qū)ο蟮?Javascript
客戶端的 JavaScript
概述
核心概念深入
函數(shù)式的 Javascript
對象與 JSON
前端 JavaScript 框架
基本概念
數(shù)組
閉包
正則表達式
函數(shù)

函數(shù)式的 Javascript

要說 JavaScript 和其他較為常用的語言最大的不同是什么,那無疑就是 JavaScript 是函數(shù)式的語言,函數(shù)式語言的特點如下:

函數(shù)為第一等的元素,即人們常說的一等公民。就是說,在函數(shù)式編程中,函數(shù)是不依賴于其他對象而獨立存在的(對比與Java,函數(shù)必須依賴對象,方法是對象的方法)。

函數(shù)可以保持自己內(nèi)部的數(shù)據(jù),函數(shù)的運算對外部無副作用(修改了外部的全局變量的狀態(tài)等),關(guān)于函數(shù)可以保持自己內(nèi)部的數(shù)據(jù)這一特性,稱之為閉包。我們可以來看一個簡單的例子:

var outter = function(){  
    var x = 0;  
    return function(){  
       return x++;  
    }  
}  

var a = outter();  
print(a());  
print(a());  

var b = outter();  
print(b());  
print(b());

運行結(jié)果為:

0
1
0
1

變量 a 通過閉包引用 outter 的一個內(nèi)部變量,每次調(diào)用 a()就會改變此內(nèi)部變量,應(yīng)該注意的是,當調(diào)用a時,函數(shù) outter 已經(jīng)返回了,但是內(nèi)部變量x的值仍然被保持。而變量b也引用了 outter,但是是一個不同的閉包,所以 b 開始引用的 x 值不會隨著 a()被調(diào)用而改變,兩者有不同的實例,這就相當于面向?qū)ο笾械牟煌瑢嵗龘碛胁煌乃接袑傩?,互不干涉?/p>

由于 JavaScript 支持函數(shù)式編程,我們隨后會發(fā)現(xiàn) JavaScript 許多優(yōu)美而強大的能力,這些能力得力于以下主題:匿名函數(shù),高階函數(shù),閉包及柯里化等。熟悉命令式語言的開發(fā)人員可能對此感到陌生,但是使用 lisp, scheme 等函數(shù)式語言的開發(fā)人員則覺得非常親切。

匿名函數(shù)

匿名函數(shù)在函數(shù)式編程語言中,術(shù)語成為 lambda 表達式。顧名思義,匿名函數(shù)就是沒有名字的函數(shù),這個是與日常開發(fā)中使用的語言有很大不同的,比如在 C/Java 中,函數(shù)和方法必須有名字才可以被調(diào)用。在 JavaScript 中,函數(shù)可以沒有名字,而且這一個特點有著非凡的意義:

function func(){  
    //do something  
}  

var func = function(){  
    //do something  
} 

這兩個語句的意義是一樣的,它們都表示,為全局對象添加一個屬性func,屬性func的值為一個函數(shù)對象,而這個函數(shù)對象是匿名的。匿名函數(shù)的用途非常廣泛,在JavaScript代碼中,我們經(jīng)??梢钥吹竭@樣的代碼:

var mapped = [1, 2, 3, 4, 5].map(function(x){return x * 2});  
print(mapped);

應(yīng)該注意的是,map這個函數(shù)的參數(shù)是一個匿名函數(shù),你不需要顯式的聲明一個函數(shù),然后將其作為參數(shù)傳入,你只需要臨時聲明一個匿名的函數(shù),這個函數(shù)被使用之后就別釋放了。在高階函數(shù)這一節(jié)中更可以看到這一點。

高階函數(shù)

通常,以一個或多個函數(shù)為參數(shù)的函數(shù)稱之為高階函數(shù)。高階函數(shù)在命令式編程語言中有對應(yīng)的實現(xiàn),比如C語言中的函數(shù)指針,Java 中的匿名類等,但是這些實現(xiàn)相對于命令式編程語言的其他概念,顯得更為復雜。

JavaScript 中的高階函數(shù)

Lisp中,對列表有一個map操作,map接受一個函數(shù)作為參數(shù),map對列表中的所有元素應(yīng)用該函數(shù),最后返回處理后的列表(有的實現(xiàn)則會修改原列表),我們在這一小節(jié)中分別用JavaScript/C/Java來對map操作進行實現(xiàn),并對這些實現(xiàn)方式進行對比:

Array.prototype.map = function(func /*, obj */){  
    var len = this.length;  
    //check the argument  
    if(typeof func != "function"){  
       throw new Error("argument should be a function!");  
    }  

    var res = [];  
    var obj = arguments[1];  
    for(var i = 0; i < len; i++){  
       //func.call(), apply the func to this[i]  
       res[i] = func.call(obj, this[i], i, this);  
    }  

    return res;  
}  

我們對 JavaScript 的原生對象 Array 的原型進行擴展,函數(shù) map 接受一個函數(shù)作為參數(shù),然后對數(shù)組的每一個元素都應(yīng)用該函數(shù),最后返回一個新的數(shù)組,而不影響原數(shù)組。由于 map 函數(shù)接受的是一個函數(shù)作為參數(shù),因此 map 是一個高階函數(shù)。我們進行測試如下:

function double(x){  
    return x * 2;  
}  

[1, 2, 3, 4, 5].map(double);//return [2, 4, 6, 8, 10]

應(yīng)該注意的是 double 是一個函數(shù)。根據(jù)上一節(jié)中提到的匿名函數(shù),我們可以為 map 傳遞一個匿名函數(shù):

var mapped = [1, 2, 3, 4, 5].map(function(x){return x * 2});  
print(mapped);

這個示例的代碼與上例的作用是一樣的,不過我們不需要顯式的定義一個double函數(shù),只需要為map函數(shù)傳遞一個“可以將傳入?yún)?shù)乘2并返回”的代碼塊即可。再來看一個例子:

[  
    {id : "item1"},  
    {id : "item2"},  
    {id : "item3"}  
].map(function(current){  
    print(current.id);  
});  

將會打?。?/p>

item1
item2
item3

也就是說,這個 map 的作用是將傳入的參數(shù)(處理器)應(yīng)用在數(shù)組中的每個元素上,而不關(guān)注數(shù)組元素的數(shù)據(jù)類型,數(shù)組的長度,以及處理函數(shù)的具體內(nèi)容。

C 語言中的高階函數(shù)

C 語言中的函數(shù)指針,很容易實現(xiàn)一個高階函數(shù)。我們還以 map 為例,說明在 C 語言中如何實現(xiàn):

//prototype of function  
void map(int* array, int length, int (*func)(int));  

map 函數(shù)的第三個參數(shù)為一個函數(shù)指針,接受一個整型的參數(shù),返回一個整型參數(shù),我們來看看其實現(xiàn):

//implement of function map  
void map(int* array, int length, int (*func)(int)){  
    int i = 0;  
    for(i = 0; i < length; i++){  
       array[i] = func(array[i]);  
    }  
}  

我們在這里實現(xiàn)兩個小函數(shù),分別計算傳入?yún)?shù)的乘 2 的值,和乘 3 的值,然后進行測試:

int twice(int num) { return num * 2; }  
int triple(int num){ return num * 3; }  

//function main  
int main(int argc, char** argv){  
    int array[5] = {1, 2, 3, 4, 5};  
    int i = 0;  
    int len = 5;  

    //print the orignal array  
    printArray(array, len);  

    //mapped by twice  
    map(array, len, twice);  
    printArray(array, len);  

    //mapped by twice, then triple  
    map(array, len, triple);  
    printArray(array, len);  

    return 0;  
} 

運行結(jié)果如下:

1 2 3 4 5
2 4 6 8 10
6 12 18 24 30

應(yīng)該注意的是 map 的使用方法,如 map(array, len, twice)中,最后的參數(shù)為 twice,而 twice 為一個函數(shù)。因為 C 語言中,函數(shù)的定義不能嵌套,因此不能采用諸如 JavaScript 中的匿名函數(shù)那樣的簡潔寫法。

雖然在 C 語言中可以通過函數(shù)指針的方式來實現(xiàn)高階函數(shù),但是隨著高階函數(shù)的“階”的增高,指針層次勢必要跟著變得很復雜,那樣會增加代碼的復雜度,而且由于 C 語言是強類型的,因此在數(shù)據(jù)類型方面必然有很大的限制。

Java 中的高階函數(shù)

Java 中的匿名類,事實上可以理解成一個教笨重的閉包(可執(zhí)行單元),我們可以通過 Java 的匿名類來實現(xiàn)上述的 map 操作,首先,我們需要一個對函數(shù)的抽象:

interface Function{  
   int execute(int x);  
}  

我們假設(shè) Function 接口中有一個方法 execute,接受一個整型參數(shù),返回一個整型參數(shù),然后我們在類 List 中,實現(xiàn) map 操作:

private int[] array;  

public List(int[] array){  
   this.array = array;  
}  

public void map(Function func){  
   for(int i = 0, len = this.array.length; i < len; i++){  
       this.array[i] = func.execute(this.array[i]);   
   }  
}  

map 接受一個實現(xiàn)了 Function 接口的類的實例,并調(diào)用這個對象上的 execute 方法來處理數(shù)組中的每一個元素。我們這里直接修改了私有成員 array,而并沒有創(chuàng)建一個新的數(shù)組。好了,我們來做個測試:

public static void main(String[] args){  
   List list = new List(new int[]{1, 2, 3, 4, 5});  
   list.print();  
   list.map(new Function(){  
       public int execute(int x){  
          return x * 2;  
       }  
   });  
   list.print();  

   list.map(new Function(){  
       public int execute(int x){  
          return x * 3;  
       }  
   });  
   list.print();  
}  

同前邊的兩個例子一樣,這個程序會打?。?/p>

1 2 3 4 5
2 4 6 8 10
6 12 18 24 30

灰色背景色的部分即為創(chuàng)建一個匿名類,從而實現(xiàn)高階函數(shù)。很明顯,我們需要傳遞給map的是一個可以執(zhí)行 execute 方法的代碼。而由于 Java 是命令式的編程語言,函數(shù)并非第一位的,函數(shù)必須依賴于對象,附屬于對象,因此我們不得不創(chuàng)建一個匿名類來包裝這個 execute 方法。而在 JavaScript 中,我們只需要傳遞函數(shù)本身即可,這樣完全合法,而且代碼更容易被人理解。

閉包與柯里化

閉包和柯里化都是 JavaScript 經(jīng)常用到而且比較高級的技巧,所有的函數(shù)式編程語言都支持這兩個概念,因此,我們想要充分發(fā)揮出 JavaScript 中的函數(shù)式編程特征,就需要深入的了解這兩個概念,我們在第七章中詳細的討論了閉包及其特征,閉包事實上更是柯里化所不可缺少的基礎(chǔ)。

柯里化的概念

閉包的我們之前已經(jīng)接觸到,先說說柯里化??吕锘褪穷A先將函數(shù)的某些參數(shù)傳入,得到一個簡單的函數(shù),但是預先傳入的參數(shù)被保存在閉包中,因此會有一些奇特的特性。比如:

var adder = function(num){  
    return function(y){  
       return num + y;    
    }  
}  

var inc = adder(1);  
var dec = adder(-1);  

這里的 inc/dec 兩個變量事實上是兩個新的函數(shù),可以通過括號來調(diào)用,比如下例中的用法:

//inc, dec現(xiàn)在是兩個新的函數(shù),作用是將傳入的參數(shù)值(+/-)1  
print(inc(99));//100  
print(dec(101));//100  

print(adder(100)(2));//102  
print(adder(2)(100));//102  

柯里化的應(yīng)用

根據(jù)柯里化的特性,我們可以寫出更有意思的代碼,比如在前端開發(fā)中經(jīng)常會遇到這樣的情況,當請求從服務(wù)端返回后,我們需要更新一些特定的頁面元素,也就是局部刷新的概念。使用局部刷新非常簡單,但是代碼很容易寫成一團亂麻。而如果使用柯里化,則可以很大程度上美化我們的代碼,使之更容易維護。我們來看一個例子:

//update會返回一個函數(shù),這個函數(shù)可以設(shè)置id屬性為item的web元素的內(nèi)容  
function update(item){  
    return function(text){  
       $("div#"+item).html(text);  
    }  
}  

//Ajax請求,當成功是調(diào)用參數(shù)callback  
function refresh(url, callback){  
    var params = {  
       type : "echo",  
       data : ""  
    };  

    $.ajax({  
       type:"post",  
       url:url,  
       cache:false,  
       async:true,  
       dataType:"json",  
       data:params,  

       //當異步請求成功時調(diào)用  
       success: function(data, status){  
           callback(data);  
       },  

       //當請求出現(xiàn)錯誤時調(diào)用  
       error: function(err){  
           alert("error : "+err);  
       }  
    });  
}  

refresh("action.do?target=news", update("newsPanel"));  
refresh("action.do?target=articles", update("articlePanel"));  
refresh("action.do?target=pictures", update("picturePanel"));  

其中,update 函數(shù)即為柯里化的一個實例,它會返回一個函數(shù),即:

update("newsPanel") = function(text){  
    $("div#newsPanel").html(text);  
} 

由于update(“newsPanel”)的返回值為一個函數(shù),需要的參數(shù)為一個字符串,因此在refresh的Ajax調(diào)用中,當success時,會給callback傳入服務(wù)器端返回的數(shù)據(jù)信息,從而實現(xiàn)newsPanel面板的刷新,其他的文章面板articlePanel,圖片面板picturePanel的刷新均采取這種方式,這樣,代碼的可讀性,可維護性均得到了提高。

一些例子

函數(shù)式編程風格

通常來講,函數(shù)式編程的謂詞(關(guān)系運算符,如大于,小于,等于的判斷等),以及運算(如加減乘數(shù)等)都會以函數(shù)的形式出現(xiàn),比如:

a > b  

通常表示為:

gt(a, b)//great than  

因此,可以首先對這些常見的操作進行一些包裝,以便于我們的代碼更具有“函數(shù)式”風格:

function abs(x){ return x>0?x:-x;}  
function add(a, b){ return a+b; }  
function sub(a, b){ return a-b; }  
function mul(a, b){ return a*b; }  
function div(a, b){ return a/b; }  
function rem(a, b){ return a%b; }  
function inc(x){ return x + 1; }  
function dec(x){ return x - 1; }  
function equal(a, b){ return a==b; }  
function great(a, b){ return a>b; }  
function less(a, b){ return a<b; }  
function negative(x){ return x<0; }  
function positive(x){ return x>0; }  
function sin(x){ return Math.sin(x); }  
function cos(x){ return Math.cos(x); } 

如果我們之前的編碼風格是這樣:

// n*(n-1)*(n-2)*...*3*2*1  
function factorial(n){  
    if(n == 1){  
        return 1;  
    }else{  
        return n * factorial(n - 1);  
    }  
}  

在函數(shù)式風格下,就應(yīng)該是這樣了:

function factorial(n){  
    if(equal(n, 1)){  
        return 1;  
    }else{  
        return mul(n, factorial(dec(n)));  
    }  
}

函數(shù)式編程的特點當然不在于編碼風格的轉(zhuǎn)變,而是由更深層次的意義。比如,下面是另外一個版本的階乘實現(xiàn):

/* 
 *  product <- counter * product 
 *  counter <- counter + 1 
 * */  

function factorial(n){  
    function fact_iter(product, counter, max){  
        if(great(counter, max)){  
            return product;  
        }else{  
            fact_iter(mul(counter, product), inc(counter), max);  
        }  
    }  

    return fact_iter(1, 1, n);  
}  

雖然代碼中已經(jīng)沒有諸如+/-/*//之類的操作符,也沒有>,<,==,之類的謂詞,但是,這個函數(shù)仍然算不上具有函數(shù)式編程風格,我們可以改進一下:

function factorial(n){  
    return (function factiter(product, counter, max){  
       if(great(counter, max)){  
           return product;  
       }else{  
           return factiter(mul(counter, product), inc(counter), max);  
       }  
    })(1, 1, n);  
}  

factorial(10);

通過一個立即運行的函數(shù) factiter,將外部的 n 傳遞進去,并立即參與計算,最終返回運算結(jié)果。

Y-結(jié)合子

提到遞歸,函數(shù)式語言中還有一個很有意思的主題,即:如果一個函數(shù)是匿名函數(shù),能不能進行遞歸操作呢?如何可以,怎么做?我們還是來看階乘的例子:

function factorial(x){  
    return x == 0 ? 1 : x * factorial(x-1);    
}

factorial 函數(shù)中,如果 x 值為 0,則返回1,否則遞歸調(diào)用 factorial,參數(shù)為 x 減 1,最后當 x 等于 0 時進行規(guī)約,最終得到函數(shù)值(事實上,命令式程序語言中的遞歸的概念最早即來源于函數(shù)式編程中)?,F(xiàn)在考慮:將 factorial 定義為一個匿名函數(shù),那么在函數(shù)內(nèi)部,在代碼x*factorial(x-1)的地方,這個 factorial 用什么來替代呢?

lambda 演算的先驅(qū)們,天才的發(fā)明了一個神奇的函數(shù),成為 Y- 結(jié)合子。使用 Y- 結(jié)合子,可以做到對匿名函數(shù)使用遞歸。關(guān)于 Y- 結(jié)合子的發(fā)現(xiàn)及推導過程的討論已經(jīng)超出了本部分的范圍,有興趣的讀者可以參考附錄中的資料。我們來看看這個神奇的 Y- 結(jié)合子:

var Y = function(f) {  
  return (function(g) {  
    return g(g);  
  })(function(h) {  
    return function() {  
      return f(h(h)).apply(null, arguments);  
    };  
  });  
};

我們來看看如何運用 Y-結(jié)合子,依舊是階乘這個例子:

var factorial = Y(function(func){  
    return function(x){  
       return x == 0 ? 1 : x * func(x-1);  
    }  
});  

factorial(10); 

或者:

Y(function(func){  
    return function(x){  
       return x == 0 ? 1 : x * func(x-1);  
    }  
})(10);  

不要被上邊提到的 Y-結(jié)合子的表達式嚇到,事實上,在 JavaScript 中,我們有一種簡單的方法來實現(xiàn) Y-結(jié)合子:

var fact = function(x){  
   return x == 0 : 1 : x * arguments.callee(x-1);  
}  

fact(10);  

或者:

(function(x){  
   return x == 0 ? 1 : x * arguments.callee(x-1);  
})(10);//3628800  

其中,arguments.callee 表示函數(shù)的調(diào)用者,因此省去了很多復雜的步驟。

其他實例

下面的代碼則頗有些“開發(fā)智力”之功效:

//函數(shù)的不動點  
function fixedPoint(fx, first){  
    var tolerance = 0.00001;  
    function closeEnough(x, y){return less( abs( sub(x, y) ), tolerance)};  
    function Try(guess){//try 是javascript中的關(guān)鍵字,因此這個函數(shù)名為大寫  
        var next = fx(guess);  
        //print(next+" "+guess);  
        if(closeEnough(guess, next)){  
            return next;  
        }else{  
            return Try(next);  
        }  
    };  
    return Try(first);  
}  
// 數(shù)層嵌套函數(shù),  
function sqrt(x){  
    return fixedPoint(  
        function(y){  
            return function(a, b){ return div(add(a, b),2);}(y, div(x, y));  
        },  
        1.0);  
}  

print(sqrt(100));  

fiexedPoint 求函數(shù)的不動點,而 sqrt 計算數(shù)值的平方根。這些例子來源于《計算機程序的構(gòu)造和解釋》,其中列舉了大量的計算實例,不過該書使用的是 scheme 語言,在本書中,例子均被翻譯為 JavaScript。

上一篇:數(shù)組下一篇:閉包