深入理解JavaScript高階函數

javascrip是一門函數式的編程語言,瞭解和熟悉高階函數對我們掌握這門語言非常有用。

高階函數相關概念

函數式編程

在大多數簡單的術語中,函數編程是一種編程形式,您可以將函數作爲參數傳遞給其他函數,並將它們作爲值返回。在函數式編程中,我們根據函數思考和編碼。

JavaScript,Haskell,Clojure,Scala和Erlang是實現函數式編程的一些語言。

一等函數

如果您一直在學習JavaScript,您可能聽說過JavaScript將函數視爲一等公民。那是因爲在JavaScript或任何其他函數式編程語言中,函數是對象。
在JavaScript中,函數是一種特殊類型的對象。他們是Function對象。
在JavaScript中,您可以使用其他類型(如對象,字符串或數字)執行的所有操作函數都可以執行。您可以將它們作爲參數傳遞給其他函數(回調函數),將它們分配給變量並傳遞它們等等。這就是JavaScript中的函數被稱爲First-Class函數(一等函數)的原因。

高階函數

Higher-Order function is a function that receives a function as an argument or returns the function as output。
我們將參數或返回值爲函數的函數稱爲高階函數。

高階函數的應用

Function 參數

  • Array.prototype.map
  • Array.prototype.reduce
  • Array.prototype.filter
  • Array.prototype.sort

我們計算一下數組的平方結果:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>

    //數組的值x2

    //普通方法
    function normalFunc() {
        const arr1 = [1, 2, 3];
        const arr2 = [];

        for(let i = 0; i < arr1.length; i++) {
            arr2.push(arr1[i] * arr1[i]);
        }
        console.log(arr2) // [1, 4, 9]
    }

    //高階函數
    function higherOrder() {
        const arr1 = [1, 2, 3];

        const arr2 = arr1.map( function callback(element, index, array) {
            return element*element;
        });

        console.log(arr2); // [1, 4, 9]
    }

    //ES6版本
    function higherOrderArrow() {
        const arr1 = [1, 2, 3];

        const arr2 = arr1.map(e => e*e);

        console.log(arr2); // [1, 4, 9]
    }

    normalFunc();
    higherOrder();
    higherOrderArrow();

</script>
</body>
</html>

上面我們使用的Array.prototype.map就是高階函數的使用,使用map對數據求平方。

Fucntion 返回值

  • Function.prototype.bind
    function introduce(firstName) {
        return `Hello, I am ${firstName} ${this.lastName}`
    }
    //
    let jason = {lastName: 'Xu'};
    let introcucation = introduce.bind(jason);
    console.log(introcucation());//Hello, I am Jason Xu

注意我們使用的 Function.prototype.bind返回的是一個函數,如果需要執行需求再次調用
introcucation()方法。

接下來我們自己模擬寫一個類似Function.prototype.bind的方法,簡單版本的。

function introduce(firstName) {
        return `Hello, I am ${firstName} ${this.lastName}`
    }
    //
    let jason = {lastName: 'Xu'};
    Function.prototype.audaqueBind = function (context, ...args) {
        let _this = this;
        return function newFunc() {
            return _this.apply(context, ...args, arguments);
        }
    }
    let introcucation = introduce.audaqueBind(jason);
    console.log(introcucation('Jason'));//Hello, I am Jason Xu

其實這輸出的結果和上面的是一樣的:Hello, I am Jason Xu
Function.prototype.audaqueBind()方法實現了Function.prototype.bind的功能這會是不是覺得學習高階函數很有用了。
如果對bind的用法和作用,詳情請看以前寫的一篇文章。
javascript Function中 bind()、call()、 apply()用法詳解

函數柯里化

在數學和計算機科學中,柯里化是一種將使用多個參數的函數轉換成一系列使用一個參數的函數,並且返回接受餘下的參數而且返回結果的新函數的技術。

柯里化(currying)又稱部分求值。一個currying的函數首先會接受一些參數,接受這些參數之後,函數並不會立即求值,而是繼續返回另一個函數,剛纔傳入的參數在函數形成的閉包中被保存起來。待到函數被真正需要求值的時候,之前傳入的所有參數都會被一次性用於求值。

  • 提高函數的適用性
  • 實現延遲執行
  • 固定易變因素

柯里化可以提前把易變因素,傳參固定下來,生成一個更明確的應用函數。
我們來看一下柯里化代碼實現

const add = (...args) => args.reduce((a, b) => a + b)

    // 簡化寫法
    function currying(func) {
        const args = []
        return function result(...rest) {
            if (rest.length === 0) {
                return func(...args)
            } else {
                args.push(...rest)
                return result
            }
        }
    }

    const sum = currying(add);

    // sum(1, 2)(3);	// 未真正求值,收集參數的和
    // sum(4);// 未真正求值,收集參數的和
    // console.log(sum());// 輸出 10

    sum(1,2)(3, 4)
    console.log(sum());// 輸出 10

高階函數與AOP

AOP爲Aspect OrientedProgramming的縮寫,意爲:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術
(AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。)

主要意圖

將日誌記錄,性能統計,安全控制,事務處理,異常處理等代碼從業務邏輯代碼中劃分出來,通過對這些行爲的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行爲的時候不影響業務邏輯的代碼。。

例如:日誌統計,異常處理等。把這些功能抽離出來後,通過"動態植入"的方法,摻入到業務邏輯模塊中。這樣做的好處是保證業務邏輯模塊的純淨和高內聚,其次可以方便的複用日誌統計等功能模塊

案例

說了這麼多 我們來看一下AOP的使用場景。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    Function.prototype.before = function (beforefn) {
        let _self = this;
        return function () {
            beforefn.apply(this, arguments);
            return _self.apply(this, arguments);
        }
    };
    Function.prototype.after = function (afterfn) {
        let _self = this;
        return function () {
            let ret = _self.apply(this, arguments);
            afterfn.apply(this, arguments);
            return ret;
        }
    };

    let func = function () {
        console.log('func');
    };
    func = func.before(function (){
        console.log('before');
    }).after(function () {
        console.log('after');
    });
    func();
</script>
</body>
</html>

我們先來看一下瀏覽器打印的結果:
before
func
after
這是不是很神奇,這樣一來我們就可以給我們func這個函數動態的加一些功能了,可以在這個函數執行前後做一些處理。

我們舉一個例子,我們幫別人打代理遊戲,我們代理主要是來收費。其實核心的還是打遊戲就相當於(func函數),至於如何來收費,我們可以動態的去切入,比如我們可以按小時,按天,甚至包月等等服務。我們可以在打代碼遊戲之前做一些動作,記錄開始打遊戲的時間,賬號等等。在遊戲後面可以統計打了多久,如果來計算費用等等。這樣是不是很方便。

實現自己的高階函數

Array.prototype.audaqueMap = function (fn, thisValue) {
        let arr = thisValue || this;
        let result = []
        if (typeof fn !== 'function') {
            throw new TypeError(fn + ' is not a function');
        }
        for (let i = 0; i < arr.length; i++) {
            let r = fn.call(arr, arr[i], i, arr)
            result.push(r)
        }
        return result
    }

    let arr = [4, 9, 16, 25];
    let result = arr.audaqueMap((item, index, arr) => {
        return item * 2
    });
    console.log(result);

    result = arr.audaqueMap((item, index, arr) => {
        return item + 1;
    });
    console.log(result);

這裏我們實現了一個Array.prototype.audaqueMap()方法,這個是不是和Array.prototype.map()實現了相同的功能。這樣一來我們可以使用高階函數來實現對原型方法的加強版本了,這個是不是很有用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章