函數——高階函數(函數式編程&函數柯里化&compose函數)


  一、概念

    了解高階函數前我們先要了解什么是函數式編程,什么是一等函數。

    函數式編程:函數式編程是一種編程方式,支持函數作為第一類對象,是一種強調以函數使用為主的軟件開發風格。函數式編程的主要目的是使用函數來抽象作用在數據之上的控制流和操作,從而在系統中消除副作用並減少對狀態的改變。它屬於“結構化編程”的一種主要思想是把運算過程盡量寫成一系列嵌套的函數調用。JavaScript,Scala等是實現函數式編程的一些語言。

    一等函數:也可以理解成函數是“第一等公民”。所謂的“第一等公民”指的是函數與其它的數據類型一樣,可以賦值給其它變量也可以作為參數進行傳遞或者是作為函數的返回值。在js中,函數是一種特殊類型的對象(Functioin對象)。js將函數視為一等公民,因為函數與其它數據類型一樣,對於可以使用其它數據類型執行的操作,函數都是可以執行的,所以js中的函數可以被稱之為一等函數。

    <script>
        //一等函數
            //把函數賦值給變量
            var fn = function () { }; //函數作為參數
 setInterval(() => { console.log('hello world!'); }, 1000); //函數作為返回值
            function Test() { return function () { console.log('hello world'); } } var res = Test(); console.log(res); //ƒ (){ console.log('hello world') }
    </script>

    高階函數:高階函數的英文是"Higher-order-function"。指的是操作其它函數的函數,一般來說,有兩種情況:函數作為參數被傳遞;函數可以作為返回值輸出。js的函數

  二、高階函數的例子

  函數作為參數用於回調函數。回調函數是一個函數作為參數傳遞給另一個主函數里面(otherFunction),當那一個主函數執行完后,再執行傳入的作為參數的函數。被作為參數傳遞到主函數的那個函數就叫做回調函數。

    <script>
        function title(value) {//回調函數
 alert(value); } function main(title, value) { //主函數,title當作參數,value這個值正是title()函數需要的
 alert('我是主函數'); title(value); // 這行的title()是回調函數 
 } main(title, '我是回調函數') //調用的是main函數,先執行main()這個主函數,title()被main()在函數體中執行一次,更能體現title()是回調函數
    </script>

  函數作為參數使用,用於數組Array.prototype.map, Array.prototype.reduce, Array.prototype.filter,Array.prototype.sort。下面分別來介紹。

   Array.prototype.map

    語法:var new_array=arr.map(callback [,thisArg])

    定義:map方法返回一個新數組,數組中的元素為原始數組元素調用函數處理的后值。  

    參數:callback生成新數組元素的函數,具有三個參數(currentValue:當前元素必需,index:當前元素的索引可選,array:map方法調用的數組,可選)

       thisArg:執行callback函數時值被用作this,可選。

    返回值:一個新的數組,對原數組沒有影響。

    <script>
        function fn(arr) { return arr.map(function (item) { return item * item; }) } var arr = [1, 2, 3, 4]; console.log(fn(arr)); //(4) [1, 4, 9, 16]
 const birthYear = [1994, 1998, 2003, 1987]; const ages = birthYear.map(year => 2019 - year); console.log(ages); //(4) [25, 21, 16, 32]
    </script>

  Array.prototype.reduce

    語法:var new_array=arr.reduce(callback [,initialValue])

    定義:reduce方法接收一個函數作為累加器,對數組的每個成員執行回調函數。

    參數:callback執行數組中的每個值(如果沒有提供initialValue則第一個值除外),有四個參數(accumulator累計器累計回調的返回值必需,currentValue:數組中正在處理的數組必需,index:索引可選,array:數組可選)

       initialValue: 作為第一次調用callback函數時的第一個參數的值,如果沒有提供初始值,那就使用數組中的第一個元素,在沒有初始值的空數組上調用reduce方法報錯。

    返回值:累加后的結果

const ary = [1, 2, 3, 4, 5]; // const sum = ary.reduce((num, item) => { // /* // *第一次:1 2 // *第二次:3 3 // *...... // *reduce只傳遞一個回調函數,那么num第一次默認是第一項 // *后續的n是上一次函數執行的處理結果 // */ //   //console.log(num, item); // return num + item; // }); // console.log(sum);
 result = ary.reduce((num, item) => { console.log(num, item); return num + item; }, 0) // return的第二個參數就是給num賦值,item從數組第一項開始遍歷
console.log(result); 

     需要注意的是:第二項不傳,num的值是數組中的第一項,第二項傳了,num就是你傳入的那個值,num除了第一次有這樣的特殊性以外,后面是每遍歷數組中的一項,num就是上一次執行函數的結果  

  Array.prototype.filter

    語法:var Array=arr.filter(callback [,thisArg])

    定義:filter方法創建一個新數組, 其包含通過所提供函數實現的測試的所有元素

    參數:callback:用來測試數組的每個元素的函數,調用時使用參數(element,index,array)返回true保留,false不保留。

       thisArg:可選,執行callback時的用於this的值,否則callback在非嚴格模式下this的值為全局對象,嚴格模式下為undefined.

    返回值:一個新的、由通過測試的元素組成的數組,如果沒有任何數組元素通過測試,則返回空數組。不會改變原數組。

    <script>
        //找出數組中大於等於10的值
 arr = [12, 3, 45, 2, 100, 0.9]; var new_Array = arr.filter((element) => { return element >= 10; },this); console.log(new_Array,this); //(3) [12, 45, 100] Window 
    </script>

  Array.prototype.sort

    語法:arr new_array=arr.sort([compareFunction])

    定義:對數組的元素做原地的排序,並返回這個數組。 sort 排序可能是不穩定的。默認按照字符串的Unicode碼位點(code point)排序

    參數:compareFunction用來指定按某種順序進行排列的函數,它里面有兩個參數(firstE1:第一個用於比較的元素,secondE2:第二個用於比較的元素)。可選如果省略,元素按照轉換為的字符串的諸個字符的Unicode位點進行排序。

    返回值:排序后的數組。

    <script> const name = ['davina', 'lisa', 'amy', 'catherine', 'lily']; console.log(name.sort()); //(5) ["amy", "catherine", "davina", "lily", "lisa"]

        //如果指明了compareFunction那數組會按照調用這個函數的返回值排序,比較函數格式如下:
        function compare(a, b) { if (a < b) { //按某種排序標准進行比較, a 小於 b
                return -1 } if (a > b) { return 1; } //a=b
            return 0; } var numbers = [1, 2.3, 5, 0, 9, 8, 10, 34]; const num = numbers.sort((a, b) => a - b ); console.log(num); // [0, 1, 2.3, 5, 8, 9, 10, 34]
    </script>

    函數作為返回值它也有很多應用場景,讓函數繼續返回一個可執行的函數,這意味着運算過程是可以延續的。

    <script>
        function a() { alert('a'); return function () { alert('b'); } } a()(); //嵌套函數
        function fn() { alert('這是fn函數'); function fn1() { alert('fn1') } fn1(); } fn(); </script>

  三、高階函數的應用場景

  1. 實現AOP

    AOP(面向切面編程)的主要作用是把一些跟核心業務邏輯模塊無關的功能抽離出來,這些跟業務邏輯無關的功能通常包括日志統計、安全控制、異常處理等。把這些功能抽離出來之后,再通過“動態織入”的方式摻入業務邏輯模塊中。這樣做的好處首先是可以保持業務邏輯模塊的純凈和高內聚性,其次是可以很方便地復用日志統計等功能模塊。可以通過擴展Function.prototype來實現。

  2. 函數柯里化(Currying)

    函數柯里化又稱為部分求值,是把接受多個參數的函數變成接受一個單一參數的函數並且返回接受余下的參數而且返回結果的新函數的技術。簡單來說就是把一個帶有多個參數的函數拆分成一系列帶有部分參數的函數。

function add(a, b) {
  return a + b;
}
console.log(add(2, 3)); //5  這個函數有2個參數

//currying這個函數
function add1(c) {
   /*  
    *第一次執行函數,形成一個不被釋放的上下文(閉包),在閉包中我們保存下來傳遞的參數信息
    *后期執行其它函數時,可以基於作用域鏈機制,找到閉包中存儲的信息,進行使用
    *類似於預先把一些信息進行存儲 
    */
    return function (d) {
        //最后小函數執行時,需要把之前傳遞的值和最新傳遞的值進行累加
    return c + d;
  };
}
console.log(add1(2)(3)); //5

  currying函數的實質其實就是預先存儲的概念,利用了閉包的特性來保存中間過程中輸入的參數。柯里化的過程是逐步傳參,逐步縮小函數的適用范圍,逐步求解的過程。

    <script> let add = function (items) { return items.reduce(function (a, b) { return a + b }); }; console.log(add([1, 2, 3, 4]));//10
        
        //把傳入的參數乖以10之后再相加
 let sum = function (items, mul) { return items.map(function (items) { return items * mul; }).reduce(function (c, d) { return c + d; }) } console.log(sum([1, 2, 3, 4], 10)) //100
    </script>

  通常的柯里化函數:把最后的一次的計算封裝進一個函數中,再把這個函數作為參數傳入柯里化函數。這樣做的話調用清楚明了。如果累加多值,則多值傳入。

    <script> let currying = function (fn) { var args = []; return function () {   //沒有參數
                if (arguments.length === 0) { return fn.apply(this, args);
            //有參數
}
else { Array.prototype.push.apply(args, [].slice.call(arguments)); return arguments.callee; } } }; var mul = function () { var total = 0; for (var i = 0, c; c = arguments[i++];) { total += c; } return total; } var sum = currying(mul); sum(100)(200)(300); console.log(sum()); //600 (空白調用時才是真正的計算) </script>

  currying具有:延遲計算,參數復用,動態生成函數的作用下面來一一介紹。

    延遲計算:上面的求和可以看出

    參數復用:實現參數復用是currying的主要用途之一。下面是一個正則的驗證。如果有多個地方都要檢查是否有數字需要將reg參數進行多次的復用,那下面那種方法最方便 

    <script>
        function check(reg, str) { return reg.test(str); } console.log(check(/\d+/g, 'dvaina')); //false
 console.log(check(/[a-z]+/g, 'davina')); //true
        // currying后
        function curryingCheck(reg) { return function (str) { return reg.test(str); } } var num = curryingCheck(/\d+/g); var Str = curryingCheck(/[a-z]+/g) console.log(num('davina')) //false
 console.log(Str('wanghao'));//true
    </script>

    反柯里化(unCurrying):從字面上我們可以看出它的意義和用法與函數柯里化相反正好相反,擴大適用范圍,創建一個應用范圍廣的函數。下面是一個簡單的實現:

    <script> let uncurrying = function (fn) { return function () { var args = [].slice.call(arguments, 1); return fn.apply(arguments[0], args); } } var test = 'a,b,c'; var split = uncurrying(String.prototype.split); console.log(split(test, ',')) </script>

  把uncurrying單獨封裝成一個函數,使用時調用 uncurrying 並傳入一個現有函數fn, 反柯里化函數會返回一個新函數,該新函數接受的第一個實參將綁定為fn中this的上下文,其他參數將傳遞給 fn作為參數。 

  3. compose函數

   在函數式編程中有一個很重要的概念那就是函數組合,實例上就是把處理數據的函數像管道一樣連接起來,然后讓數據穿過管道得到最終的結果,如下面代碼所示:

let add1 = (x) => x + 1; let mul3 = (x) => x * 3; let div2 = (x) => x / 2; div2(mul3(add1(add1(0)))); //=>3

   但是這種寫法的可讀性比較差,所以就有了compose函數。compose函數是一個函數(函數只接受一個參數)的返回值作為另外一個函數的參數,將需要嵌套執行的函數平鋪。它可以使得代碼更加精練,代碼可讀性更好。

function compose(...funcs) { //...funcs接收的就是所有傳遞進來的函數
    return function anonymous(val) { //val第一個函數執行時需要的實參[div2,mul3,add1,add1] //要用到reverse()方法進行反轉
        return funcs.reverse().reduce((num, item) => { //當沒有傳入函數時
            if (funcs.length === 0) return val; //當傳入的函數為1時
            if (funcs.length === 1) return funcs[0](val); //num如果是一個函數那首先要把val傳入num中執行,把執行的結果賦值給item //num不是函數就把num當成參數傳遞給item
            return typeof num === 'function' ? item(num(val)) : item(num); }); } } let result = compose(div2, mul3, add1, add1)(0); console.log(result);

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM