一、什麼樣的函數是高階函數?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() })