高階函數

一、什麼樣的函數是高階函數?node

  1)一個函數的參數是另外一個函數(回調)react

  2)一個函數返回另外一個函數(拆分函數)數組

  如 function a(){return function(){}}閉包

二、常見的高階函數:併發

  1)before:咱們常常會遇到這種需求,就是一個核心功能上面須要衍生出一些個性化功能,這時候,咱們須要先將核心功能抽離出來,在外面再增長方法app

    解決方法:咱們能夠重寫原型上的方法,加以擴展:函數

如咱們如今有一個核心方法,toSay
function toSay(...args){
    console.log("say sth...",args)
}
如今咱們能夠在toSay的原型上增長一個before方法,這樣子全部的toSay方法也均可以調用
Function.prototype.before = function(beforeFn){
    // 一、箭頭函數中,沒有this指向,因此會向上級做用於查找,這裏的this就是調用before方法的對象。
    // 二、箭頭函數中,沒有arguments參數,因此咱們須要再return方法的時候,將參數獲取,而後再箭頭函數中傳遞使用。
    return (...args)=>{
        beforeFn();
        this(...args);
    }
}
let newSay = toSay.before(()=>{
    console.log("你好")
})
newSay("111") 
// 你好
// 說話,[111]

  上面這種將核心方法抽離出來,再在覈心功能基礎上封裝新的方法的行爲,能夠理解爲切片或者裝飾。優化

  2)react中的事物:即事物的改變,能夠在前面和後面同時增長方法。ui

const perform=(anyFn,wrappers)=>{
    wrappers.forEach(fn=>{
        fn.init();
    });
     anyFn();
    wrappers.forEach(fn=>{
        fn.finish();
    });
}
perform(()=>{
    console.log("說話")
},[
    {
        init:()=>{console.log("wrapper1...init")},
        finish:()=>{console.log("wrapper1...finish")}
    },
    {
        init:()=>{console.log("wrapper2...init")},
        finish:()=>{console.log("wrapper2...finish")}
    }
   ]
)

  上面的例子中,perform函數接收兩個參數,一個是須要執行的主體函數,一個是包裹的事物,在主體函數執行前,執行事物中的init方法,主體函數執行後,執行事物的finish方法。this

  3)柯里化:將一個函數拆成多個函數

  好比說,咱們如今須要判斷一個對象的類型,最經常使用的方式是什麼呢?

  首先想到的就是函數的toString()方法

  Object.prototype.toString().call(obj)

  那麼如何自定義實現判斷某對象是不是某種類型的函數呢?

  有了上面的toString方法,咱們立刻就能想到下面這種方法:

const checkType = (obj, type)=>{
    return Object.prototype.toString().call(obj)==`[object ${type}]`  // 注意這裏的`` 必須是反單引號(英文狀態下鍵盤上1旁邊的符號)
}
checkType('111','String')  // true

  上面這個函數實現了咱們須要的功能,可是須要用戶傳入對象和對象類型,咱們常常可能會輸入錯誤的類型,如String類型,可能會被輸入string,而咱們還會以爲 是否是程序出問題了呢?

  因此,咱們能不能將上面的方法優化一下?不用用戶輸入對象類型,只用告訴用戶調用對應函數名稱,就能夠知道知道對象是否是制定的類型呢?

  咱們將上面的函數修改一下,checkType1接收一個參數,type,而後返回一個函數,這個函數中,接收用戶須要判斷類型的對象。

  而後用戶只須要調用對應的isString或者是isNumber便可判斷對應的數據類型。以下:

const checkType1 = (type)=>{
  // 這裏還涉及到了閉包的知識,即當前函數返回值,能夠在任意做用域執行
return (obj)=>{ return Object.prototype.toString.call(obj)==`[object ${type}]` } } const isString = checkType1("String") const isNumber = checkType1("Number") isString("111") isNumber(111)

  上面的方法看起來比前面的要好了一點,用戶調用相對方便了一點點,可是咱們的程序寫的就相對囉嗦了,若是有不少種類型須要判斷的話,意味着咱們須要屢次調用checkType1方法,傳入不一樣的類型參數,並定義多個不一樣的變量去接收。

  因此咱們繼續對上面的方法進一步優化:

const checkType1 = (type)=>{
    return (obj)=>{
         return Object.prototype.toString.call(obj)==`[object ${type}]`
    }
}

let types = ["isString","isNumber","isBoolean"]
let utils = {};
types.forEach(t=>{
    utils["is"+t] = checkType1(t);
});

// 用戶在調用時,使用utils.isString('111')這種方式便可

  上面的方法種,咱們在前面方法的基礎上,作了一點改變,咱們將全部類型經過一個數組儲存起來,而後經過循環這個數組,分別給每一個類型調用checkType1方法進行校驗,並將校驗的返回結果經過對象utils存儲起來

  後面用戶想要判斷某種對象的時候,只須要使用utils.isType便可。

  這種作法的好處在於,不用再去分別手寫對每種類型的判斷,只須要將類型添加到數組中便可。

  例如,如今有以下代碼:

const add = (a,b,c,d)=>{
    return a+b+c+d
}
add(1,2,3,4)
// 如今不想一塊兒把參數傳遞完畢,但願分屢次傳遞參數,好比,咱們但願 add(1)(2)(3,4) 這樣子傳參執行,要如何實現?
// 這是一個典型的柯里化應用
const curring = (fn,arr=[])=>{
  // fn 的參數個數,fn.length
  const len = fn.length;
  return (...args)=>{
    // 這裏的args就是每次傳遞的參數,用arr存儲起來
    arr = arr.concat(args);
    // 若是傳遞的參數個數和計算函數的參數個數相等,則開始計算,不然繼續調用curring函數,收集參數。
    if(len==arr.length){
      return fn(...arr);
    }else{
      return curring(fn, arr);
    }
  }
}
let newAdd = curring(add);
newAdd(1)(2)(3,4)

 4)after函數:在作完一件事情後,執行某個函數。如調用一個函數3次後,通知另外一個函數執行

1) 先定義一個但願三次後執行的函數
const fn= ()=>{
    console.log("三次後執行我...")
}

2) 定義一個函數,接受兩個參數,一個times,一個須要執行的函數
const after= (times,fn)=>{
    return ()=>{
        if(--times==0){
            fn()
        }
    }
}

let test = after(3, fn)
test();
test();
test()'// 執行fn()

三、高階函數的應用

前面列舉了好幾種高階函數,具體有哪些狀況下會使用高階函數呢?

如咱們常見的併發問題,發佈訂閱,還有常說的觀察者模式,都是使用的高階函數。

1)併發問題:如咱們須要同時去讀取兩個文件內容,並在兩個文件讀取完畢後,將讀取的內容打印出來。這裏使用node的fs模塊

最醜的代碼是這樣的
const fs = require("fs");
let info = {};
fs.readFile("name.txt","utf8",(err,data)=>{
    if(err){
        console.log(err);
        return;
    }
    info["name"] = data;
    fs.readFile("age.txt","utf8",(err,data)=>{
        if(err){
            console.log(err);
            return;
        }
        info["age"] = data;
    }
    console.log(info)
}
上面代碼,若是咱們有多個文件須要讀取的話,須要像金字塔同樣,堆很遠...這顯然不是咱們想要的結果,那麼怎麼樣寫的比較優雅一點呢?

上面咱們講了after函數,咱們在讀取完兩個文件後,輸出文件內容,和after函數的定義是否是很像?其實這種寫法也很繁瑣,可是不會像上面的方法同樣一層層的堆疊了
const after = (times, fn)=>{
    return ()=>{
        if(--times==0){
            fn()
        }
    }
}
const out=after(2,()=>{
    console.log(info)
})

fs.readFile("name.txt","utf8",(err, data)=>{
    if(err){
        console.log(err);
        return;
    }
    info["name"] = data;
    out();
})
fs.readFile("age.txt","utf8",(err, data)=>{
    if(err){
        console.log(err);
        return;
    }
    info["age"] = data;
    out() 
})
相關文章
相關標籤/搜索