注:文章最末尾有我的公衆號二維碼,會分享更多技術文章等,敬請關注javascript
本文講解的高階函數是以前講解的閉包的續集,因此在學習高階函數以前,必定要確保對閉包以及做用域的概念已經有了解:前端
閉包詳解一git
有Java、C#等開發經驗的同窗對代碼抽象的思想必定不會陌生,抽象類、接口平時寫的很是多,可是對於一直都從事前端開發的同窗來講,「抽象」這個詞就比較陌生了,畢竟JavaScript中沒有abstract、interface。程序員
可是JS中確定是有代碼抽象的思想的,只不過是形式上和Java等語言不一樣罷了!github
先來看Java中的一個抽象類:ajax
public abstract class SuperClass {
public abstract void doSomething();
}
複製代碼
這是Java中的一個類,類裏面有一個抽象方法doSomething,如今不知道子類中要doSomething方法作什麼,因此將該方法定義爲抽象方法,具體的邏輯讓子類本身去實現。編程
建立子類去實現SuperClass:設計模式
public class SubClass extends SuperClass{
public void doSomething() {
System.out.println("say hello");
}
}
複製代碼
SubClass中的doSomething輸出字符串「say hello」,其餘的子類會有其餘的實現,這就是Java中的抽象類與實現。數組
那麼JS中的抽象是怎麼樣的,最爲經典的就是回調函數了:
function createDiv(callback) {
let div = document.createElement('div');
document.body.appendChild(div);
if (typeof callback === 'function') {
callback(div);
}
}
createDiv(function (div) {
div.style.color = 'red';
})
複製代碼
這個例子中,有一個createDiv這個函數,這個函數負責建立一個div並添加到頁面中,可是以後要再怎麼操做這個div,createDiv這個函數就不知道,因此把權限交給調用createDiv函數的人,讓調用者決定接下來的操做,就經過回調的方式將div給調用者。
這也是體現出了抽象,既然不知道div接下來的操做,那麼就直接給調用者,讓調用者去實現。 和Java中抽象類中的抽象方法的思想是同樣的。
總結一下抽象的概念:抽象就是隱藏更具體的實現細節,從更高的層次看待咱們要解決的問題。
在編程的時候,並非全部功能都是現成的,好比上面例子中,能夠建立好幾個div,對每一個div的處理均可能不同,須要對未知的操做作抽象,預留操做的入口,做爲一名程序員,咱們須要具有這種在恰當的時候將代碼抽象的思想。
接下來看一下ES5中提供的幾個數組操做方法,能夠更深刻的理解抽象的思想,ES5以前遍歷數組的方式是:
var arr = [1, 2, 3, 4, 5];
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
console.log(item);
}
複製代碼
仔細看一下,這段代碼中用for,而後按順序取值,有沒有以爲如此操做有些不夠優雅,爲出現錯誤留下了隱患,好比把length寫錯了,一不當心複用了i。既然這樣,能不能抽取一個函數出來呢?最重要的一點,咱們要的只是數組中的每個值,而後操做這個值,那麼就能夠把遍歷的過程隱藏起來:
function forEach(arr, callback) {
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
callback(item);
}
}
forEach(arr, function (item) {
console.log(item);
});
複製代碼
以上的forEach方法就將遍歷的細節隱藏起來的了,把用戶想要操做的item返回出來,在callback還能夠將i、arr自己返回:callback(item, i, arr)
。
JS原生提供的forEach方法就是這樣的:
arr.forEach(function (item) {
console.log(item);
});
複製代碼
跟forEach同族的方法還有map、some、every等。思想都是同樣的,經過這種抽象的方式可讓使用者更方便,同事又讓代碼變得更加清晰。
抽象是一種很重要的思想,讓可讓代碼變得更加優雅,而且操做起來更方便。在高階函數中也是使用了抽象的思想,因此學習高階函數得先了解抽象的思想。
至少知足如下條件的中的一個,就是高階函數:
將其餘函數做爲參數傳遞
將函數做爲返回值
簡單來講,就是一個函數能夠操做其餘函數,將其餘函數做爲參數或將函數做爲返回值。我相信,寫過JS代碼的同窗對這個概念都是很容易理解的,由於在JS中函數就是一個普通的值,能夠被傳遞,能夠被返回。
參數能夠被傳遞,能夠被返回,對Java等語言開發的同窗理解起來可能會稍微麻煩一些,由於Java語言沒有那麼的靈活,不過Java8的lambda大概就是這意思;
函數做爲參數傳遞就是咱們上面提到的回調函數,回調函數在異步請求中用的很是多,使用者想要在請求成功後利用請求回來的數據作一些操做,可是又不知道請求何時結束。
用jQuery來發一個Ajax請求:
function getDetailData(id, callback) {
$.ajax('http://xxxxyyy.com/getDetailData?' + id, function (res) {
if (typeof callback === 'function') {
callback(res);
}
});
}
getDetailData('78667', function (res) {
// do some thing
});
複製代碼
相似Ajax這種操做很是適合用回調去作,當一個函數裏不適合執行一些具體的操做,或者說不知道要怎麼操做時,能夠將相應的數據傳遞給另外一個函數,讓另外一個函數來執行,而這個函數就是傳遞進來的回調函數。
另外一個典型的例子就是數組排序。
在判斷數據類型的時候最經常使用的是typeof,可是typeof有必定的侷限性,好比:
console.log(typeof []); // 輸出object
console.log(typeof {}); // 輸出object
複製代碼
判斷數組和對象都是輸出object,若是想要更細緻的判斷應該要使用Object.prototype.toString
console.log(Object.prototype.toString.call([])); // 輸出[object Array]
console.log(Object.prototype.toString.call({})); // 輸出[object Object]
複製代碼
基於此,咱們能夠寫出判斷對象、數組、數字的方法:
function isObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]';
}
function isArray(arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
function isNumber(number) {
return Object.prototype.toString.call(number) === '[object Number]';
}
複製代碼
咱們發現這三個方法太像了,能夠作一些抽取:
function isType(type) {
return function (obj) {
return Object.prototype.toString.call(obj) === '[object ' + type + ']';
}
}
var isArray = isType('Array');
console.log(isArray([1,2]));
複製代碼
這個isType方法就是高階函數,該函數返回了一個函數,而且利用閉包,將代碼變得優雅。
高階函數在平時的開發中用的很是多,只是有時候你不知道你的這種用法就是高階函數,在一些開源的類庫中也用的不少,好比頗有名的 lodash,挑其中一個before函數:
function before(n, func) {
let result
if (typeof func != 'function') {
throw new TypeError('Expected a function')
}
return function(...args) {
if (--n > 0) {
result = func.apply(this, args)
}
if (n <= 1) {
func = undefined
}
return result
}
}
複製代碼
在before函數中,同時有用到將函數當作傳遞進來,又返回了一個函數,這是一個很經典的高階函數的例子。
看一下該代碼能夠怎麼用吧:
jQuery(element).on('click', before(5, addContactToList))
複製代碼
因此before函數就是讓某個方法最多調用n次。
注:before函數代碼不難,使用也不難,但就是這麼一個簡單的工具方法須要瞭解的知識點有:做用域、閉包、高階函數,因此說知識點都是連貫的,接下來要寫的JavaScript設計模式系列,一樣也要用到這些知識。
在寫代碼的時候,大多數狀況都是由咱們本身主動去調用函數。不過在有一些狀況下,函數的調用不是由用戶直接控制的,在這種狀況下,函數有可能被廢除頻繁的調用,從而形成性能問題。
在 Element-UI 中,有一個 el-autocomplete 組件,該組件能夠在用戶輸入的時候在輸入框下方列出相關輸入項:
其實就是能夠在用戶輸入的時候,能夠用已經輸入的內容作搜索,餓了麼在實現該組件的時候是利用input組件,而且監聽用戶的輸入:
用input事件去監聽用戶輸入的話,用戶輸入的每個字都會觸發該方法,若是是要用輸入的內容去作網絡搜索,用戶輸入的每一字都搜索的話,觸發的頻率過高了,性能消耗就有點大了,並且在網絡比較差的狀況下用戶體驗也比較很差。
餓了麼實現該組件的時候固然也考慮到了這些問題,用的是業界比較通用的作法→節流,就是當輸入後,延遲一段時間再去執行搜索,若是該次延遲執行尚未完成的話,就忽略接下來搜索的請求。
看一下其實現:
autocomplete的節流思想就是剛纔說的那種,而且用了 throttle-debounce 這個工具庫,其實現就是利用高階函數,有興趣的同窗能夠看它的源碼:https://github.com/niksy/throttle-debounce,代碼並不複雜。
高階函數還有其餘的用法,好比用在設計模式中等,這些內容將會在後面詳細介紹。
能夠關注個人公衆號:icemanFE,接下來持續更新技術文章!