高階函數不會用?教你JS中最實用的幾個高階函數用法

2021-01-14 web前端開發
來源 | https://juejin.im/post/5ad6b34a6fb9a028cc61bfb3高階函數函數作為參數傳遞說實在的本來只是個簡單的🌰,不過越寫越興奮,就弄成了個小demo了,大家也可以copy下去自己添油加醋一下(寫成各種版本),樂呵一下吧,PS:由於代碼過多佔用文章,將css樣式去掉了,樣式的實現大家隨意發揮就好了

<body>

<div id="box"class="clearfix"></div>

<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>

<script src="./index.js"></script>

</body>

// index.js

// 回調函數

// 異步請求

let getInfo = function(keywords, callback) {

$.ajax({

url: 'http://musicapi.leanapp.cn/search', // 以網易雲音樂為例

data: {

keywords

},

success: function(res) {

callback && callback(res.result.songs);

}

})

};


$('#btn').on('click', function() {

let keywords = $(this).prev().val();

$('#loading').show();

getInfo(keywords, getData);

});

// 加入回車

$("#search_inp").on('keyup', function(e){

if(e.keyCode === 13) {

$('#loading').show();

getInfo(this.value, getData);

}

});


function getData(data) {

if(data && data.length) {

let html = render(data);

// 初始化Dom結構

initDom(html, function(wrap) {

play(wrap);

});

}

}

// 格式化時間戳

function formatDuration(duration) {

duration = parseInt(duration / 1000); // 轉換成秒

let hour = Math.floor(duration / 60/ 60),

min = Math.floor((duration % 3600) / 60),

sec = duration % 60,

result = '';


result += `${fillIn(min)}:${fillIn(sec)}`;

return result;

}


function fillIn(n) {

return n < 10? '0'+ n : ''+ n;

}


let initDom = function(tmp, callback) {

$('.item').remove();

$('#loading').hide();

$('#box').append(tmp);

// 這裡因為不知道dom合適才會被完全插入到頁面中

// 所以用callback當參數,等dom插入後再執行callback

callback && callback(box);

};


let render = function(data) {

let template = '';

let set= newSet(data);

data = [...set]; // 可以利用Set去做下簡單的去重,可忽略這步

for(let i = 0; i < 8; i++) {

let item = data[i];

let name = item.name;

let singer = item.artists[0].name;

let pic = item.album.picUrl;

let time = formatDuration(item.duration);


template += `

<div class="item">

<div class="pic" data-time="${time}">

<span></span>

<img src="${pic}"/>

</div>

<h4>${name}</h4>

<p>${singer}</p>

<audio src="http://music.163.com/song/media/outer/url?id=${item.id}.mp3"></audio>

</div>`;

}

return template;

};


let play = function(wrap) {

wrap = $(wrap);

wrap.on('click', '.item', function() {

let self = $(this),

$audio = self.find('audio'),

$allAudio = wrap.find('audio');


for(let i = 0; i < $allAudio.length; i++) {

$allAudio[i].pause();

}

$audio[0].play();

self.addClass('play').siblings('.item').removeClass('play');

});

};

按照上面的代碼啪啪啪,就會得到下面這樣的效果,一起來看下吧不過依然感謝網易雲音樂提供的API接口,讓我們聆聽美妙好音樂函數作為返回值輸出親們,函數作為返回值輸出的應用場景那就太多了,這也體現了函數式編程的思想。其實從閉包的例子中我們就已經看到了關於高階函數的相關內容了,哈哈還記得在我們去判斷數據類型的時候,我們都是通過Object.prototype.toString來計算的。每個數據類型之間只是'[object XXX]'不一樣罷了。所以在我們寫類型判斷的時候,一般都是將參數傳入函數中,這裡我簡單寫一下實現,咱們先來看看。

function isType(type) {

returnfunction(obj) {

returnObject.prototype.toString.call(obj) === `[object ${type}]

}

}


const isArray = isType('Array');

const isString = isType('String');

console.log(isArray([1, 2, [3,4]]); // true

console.log(isString({}); // false

其實上面實現的isType函數,也屬於偏函數的範疇,偏函數實際上是返回了一個包含預處理參數的新函數,以便之後可以調用。另外還有一種叫做預置函數,它的實現原理也很簡單,當達到條件時再執行回調函數。

function after(time, cb) {

returnfunction() {

if(--time === 0) {

cb();

}

}

}

// 舉個慄子吧,吃飯的時候,我很能吃,吃了三碗才能吃飽

let eat = after(3, function() {

console.log('吃飽了');

});

eat();

eat();

eat();

上面的eat函數只有執行3次的時候才會輸出'吃飽了',還是比較形象的。這種預置函數也是js中巧妙的裝飾者模式的實現,裝飾者模式在實際開發中也非常有用,再以後的歲月裡我也會好好研究之後分享給大家的。

// 這裡我們創建了一個單例模式

let single = function(fn) {

let ret;

returnfunction() {

console.log(ret); // render一次undefined,render二次true,render三次true

// 所以之後每次都執行ret,就不會再次綁定了

return ret || (ret = fn.apply(this, arguments));

}

};


let bindEvent = single(function() {

// 雖然下面的renders函數執行3次,bindEvent也執行了3次

// 但是根據單例模式的特點,函數在被第一次調用後,之後就不再調用了

document.getElementById('box').onclick = function() {

alert('click');

}

returntrue;

});


let renders = function() {

console.log('渲染');

bindEvent();

}


renders();

renders();

renders();

這個高階函數的慄子,可以說一石二鳥啊,既把函數當做參數傳遞了,又把函數當返回值輸出了。單例模式也是一種非常實用的設計模式,在以後的文章中也會針對這些設計模式去分析的,敬請期待,哈哈,下面再看看高階函數還有哪些用途。其他應用函數柯裡化柯裡化又稱部分求值,柯裡化函數會接收一些參數,然後不會立即求值,而是繼續返回一個新函數,將傳入的參數通過閉包的形式保存,等到被真正求值的時候,再一次性把所有傳入的參數進行求值。還能闡述的更簡單嗎?在一個函數中填充幾個參數,然後再返回一個新函數,最後進行求值,沒了,是不是說的簡單了。

// 普通函數

function add(x,y){

return x + y;

}


add(3,4); // 7


// 實現了柯裡化的函數

// 接收參數,返回新函數,把參數傳給新函數使用,最後求值

let add = function(x){

returnfunction(y){

return x + y;

}

};


add(3)(4); // 7

以上代碼非常簡單,只是起個引導的作用。下面我們來寫一個通用的柯裡化函數

function curry(fn) {

let slice = Array.prototype.slice, // 將slice緩存起來

args = slice.call(arguments, 1); // 這裡將arguments轉成數組並保存


returnfunction() {

// 將新舊的參數拼接起來

let newArgs = args.concat(slice.call(arguments));

return fn.apply(null, newArgs); // 返回執行的fn並傳遞最新的參數

}

}

實現了通用的柯裡化函數,了不起啊,各位很了不起啊。不過這還不夠,我們還可以利用ES6再來實現一下,請看如下代碼:

// ES6版的柯裡化函數

function curry(fn) {

const g = (...allArgs) => allArgs.length >= fn.length ?

fn(...allArgs) :

(...args) => g(...allArgs, ...args)


return g;

}


// 測試用例

const foo = curry((a, b, c, d) => {

console.log(a, b, c, d);

});

foo(1)(2)(3)(4); // 1 2 3 4

const f = foo(1)(2)(3);

f(5); // 1 2 3 5

不過大家有沒有發現我們在ES5中使用的bind方法,其實也利用了柯裡化的思想,那麼再來看一下下。

let obj = {

songs: '以父之名'

};


function fn() {

console.log(this.songs);

}


let songs = fn.bind(obj);

songs(); // '以父之名'

為什麼這麼說?這也看不出什麼頭緒啊,別捉急,再來看一下bind的實現原理。

Function.prototype.bind = function(context) {

let self = this,

slice = Array.prototype.slice,

args = slice.call(arguments);


returnfunction() {

return self.apply(context, args.slice(1));

}

};

是不是似曾相識,是不是,是不是,有種師出同門的趕腳了啊。反柯裡化啥?反柯裡化,剛剛被柯裡化弄的手舞足蹈的,現在又出現了個反柯裡化,有木有搞錯啊!那麼反柯裡化是什麼呢?簡而言之就是函數的借用,天下函數(方法)大家用。比如,一個對象未必只能使用它自身的方法,也可以去借用原本不屬於它的方法,要實現這點似乎就很簡單了,因為call和apply就可以完成這個任務。

(function() {

// arguments就借用了數組的push方法

let result = Array.prototype.slice.call(arguments);

console.log(result); // [1, 2, 3, 'hi']

})(1, 2, 3, 'hi');


Math.max.apply(null, [1,5,10]); // 數組借用了Math.max方法

從以上代碼中看出來了,大家都是相親相愛的一家人。利用call和apply改變了this指向,方法中用到的this再也不局限在原來指定的對象上了,加以泛化後得到更廣的適用性。反柯裡化的話題是由我們親愛的js之父發表的,我們來從實際例子中去看一下它的作用。

let slice = Array.prototype.slice.uncurrying();


(function() {

let result = slice(arguments); // 這裡只需要調用slice函數即可

console.log(result); // [1, 2, 3]

})(1,2,3);

以上代碼通過反柯裡化的方式,把Array.prototype.slice變成了一個通用的slice函數,這樣就不會局限於僅對數組進行操作了,也從而將函數調用顯得更為簡潔清晰了。

Function.prototype.uncurrying = function() {

let self = this; // self 此時就是下面的Array.prototype.push方法

returnfunction() {

let obj = Array.prototype.shift.call(arguments);

/*

obj其實是這種樣子的

obj = {

'length': 1,

'0': 1

}

*/

return self.apply(obj, arguments); // 相當於Array.prototype.push(obj, 110)

}

};

let slice = Array.prototype.push.uncurrying();


let obj = {

'length': 1,

'0': 1

};

push(obj, 110);

console.log(obj); // { '0': 1, '1': 110, length: 2 }

其實實現反柯裡化的方式不只一種,下面再給大家分享一種,直接看代碼

Function.prototype.uncurrying = function() {

let self = this;

returnfunction() {

returnFunction.prototype.call.apply(self, arguments);

}

};

實現方式大致相同,大家也可以寫一下試試,動動手,活動一下筋骨函數節流下面再說一下函數節流,我們都知道在onresize、onscroll和mousemove,上傳文件這樣的場景下,函數會被頻繁的觸發,這樣很消耗性能,瀏覽器也會吃不消的於是大家開始研究一種高級的方法,那就是控制函數被觸發的頻率,也就是函數節流了。簡單說一下原理,利用setTimeout在一定的時間內,函數隻觸發一次,這樣大大降低了頻率問題。函數節流的實現也多種多樣,這裡我們實現大家常用的吧。

function throttle (fn, wait) {

let _fn = fn, // 保存需要被延遲的函數引用

timer,

flags = true; // 是否首次調用


returnfunction() {

let args = arguments,

self = this;


if(flags) { // 如果是第一次調用不用延遲,直接執行即可

_fn.apply(self, args);

flags = false;

return flags;

}

// 如果定時器還在,說明上一次還沒執行完,不往下執行

if(timer) returnfalse;


timer = setTimeout(function() { // 延遲執行

clearTimeout(timer); // 清空上次的定時器

timer = null; // 銷毀變量

_fn.apply(self, args);

}, wait);

}

}


window.onscroll = throttle(function() {

console.log('滾動');

}, 500);

給頁面上body設置一個高度出現滾動條後試試看,比每滾動一下就觸發來說,大大降低了性能的損耗,這就是函數節流的作用,起到了事半功倍的效果,開發中也比較常用的。分時函數我們知道有一個典故叫做:羅馬不是一天建成的;更為通俗的來說,胖子也不是一天吃成的。體現在程序裡也是一樣,我們如果一次獲得了很多數據(比如有10W數據),然後在前端渲染的時候會卡到爆,瀏覽器那麼溫柔的物種都會起來罵娘了。所以在處理這麼多數據的時候,我們可以選擇分批進行,不用一次塞辣麼多,嘴就辣麼大。

function timeChunk(data, fn, count = 1, wait) {

let obj, timer;


function start() {

let len = Math.min(count, data.length);

for(let i = 0; i < len; i++) {

val = data.shift(); // 每次取出一個數據,傳給fn當做值來用

fn(val);

}

}


returnfunction() {

timer = setInterval(function() {

if(data.length === 0) { // 如果數據為空了,就清空定時器

return clearInterval(timer);

}

start();

}, wait); // 分批執行的時間間隔

}

}


// 測試用例

let arr = [];

for(let i = 0; i < 100000; i++) { // 這裡跑了10萬數據

arr.push(i);

}

let render = timeChunk(arr, function(n) { // n為data.shift()取到的數據

let div = document.createElement('div');

div.innerHTML = n;

document.body.appendChild(div);

}, 8, 20);


render();

惰性加載兼容現代瀏覽器以及IE瀏覽器的事件添加方法就是一個很好的慄子。

// 常規的是這樣寫的

let addEvent = function(ele, type, fn) {

if(window.addEventListener) {

return ele.addEventListener(type, fn, false);

} elseif(window.attachEvent) {

return ele.attachEvent('on'+ type, function() {

fn.call(ele);

});

}

};

這樣實現有一個缺點,就是在調用addEvent的時候都會執行分支條件裡,其實只需要判斷一次就行了,非要每次執行都來一波。下面我們再來優化一下addEvent,以規避上面的缺點,就是我們要實現的惰性加載函數了。

let addEvent = function(ele, type, fn) {

if(window.addEventListener) {

addEvent = function(ele, type, fn) {

ele.addEventListener(type, fn, false);

}

} elseif(window.attachEvent) {

addEvent = function(ele, type, fn) {

ele.attachEvent('on'+ type, function() {

fn.call(ele)

});

}

}


addEvent(ele, type, fn);

};

上面的addEvent函數還是個普通函數,還是有分支判斷。不過當第一次進入分支條件後,在內部就會重寫了addEvent函數。下次再進入addEvent函數的時候,函數裡就不存在條件判斷了。終點節目不早,時間剛好,又到了該要說再見的時候了,來一個結束語吧。

可以把函數當做參數傳遞和返回值輸出

函數柯裡化

參數復用 (add函數慄子)

提前返回 (惰性加載)

延遲計算 (bind)

反柯裡化

函數節流

分時函數

惰性加載

相關焦點

  • Python基礎教程——高階函數
    Python的高階函數,就是map、filter、reduce,說它們是高階函數,只是因為我們平時用的少,所以理解起來也有點費勁,事實上,它們功能很強大,也很好用易用。一起來看看吧。為了避免每次學新東西總是掉進這樣那樣的坑中,我們從需求入手,理解了需求,掌握了用法,那就一通百通了。
  • python高階函數:map、filter、reduce的替代品
    什麼是高階函數?高階函數是一種將函數作為參數,或者把函數作為結果返回的函數,map函數、sorted函數就是高階函數的典型例子。map函數在小編以前的文章中做過相應的知識分享。sorted函數是python的內置函數,它的可選參數key用於提供一個函數,它可以將函數應用到各個元素上進行排序。
  • 高等數學入門——計算乘積函數高階導數的萊布尼茲公式
    例如用ε-δ語言證明函數極限這類高等數學課程不要求掌握的內容,我們不作過多介紹。本系列文章適合作為大一新生初學高等數學時的課堂同步輔導,也可作為高等數學期末複習以及考研第一輪複習時的參考資料。文章中的例題大多為紮實基礎的常規性題目和幫助加深理解的概念辨析題,並適當選取了一些考研數學試題。所選題目難度各異,對於一些難度較大或對理解所學知識有幫助的「經典好題」,我們會詳細講解。
  • Kotlin最佳實踐:在高階函數中使用inline - 碼農登陸
    前言最近,無意中看到一篇文章,是聊inline在高階函數中的性能提升,說實話之前沒有認真關注過這個特性,所以藉此機會好好學習了一番。高階函數:入參中含有lambda的函數(方法)。原文是一位外國小哥寫的,這裡把它翻譯了一下重寫梳理了一遍發出來。
  • Python學習,這些高階函數和高階特性值得一學
    解決問題的思路有的時候會比較單一,其實Python有很多靈活的解法,比如python的幾個高階函數或者特性!推導式列表推導式,使用一句表達式構造一個新列表,可包含過濾、轉換等操作。語法:{key_exp:value_exp for item in collection if codition}集合推導式語法:{exp for item in collection if codition}map函數map(fun, lst),將傳入的函數變量func作用到lst變量的每個元素中,並將結果組成新的列表返回
  • 函數高階導數與圖像凹凸性
    很多函數在定義域的某個區間內存在導數,自變量與這些導數值的集合之間的映射關係,我們稱之為導函數。有些函數的導函數仍然存在導數,我們稱之為原函數的二階導數,二階導數如果能繼續求導,就是三階導數…,二階以上的導數,我們統稱為高階導數。
  • Excel函數公式:VLOOKUP函數和IF、CHOOSE函數組合實用技巧解讀
    眾所周知,VLOOKUP函數是Excel中實用頻率非常高的查找引用函數之一,但是由於語法規則的限制,在某些功能的實現上需要藉助於其他函數來完成。例如:從右向左查詢。一、VLOOKUP和IF函數組合實用技巧。目的:根據編號來查詢對應的姓名。
  • 算法中的微積分:5大函數求導公式讓你在面試中脫穎而出
    當函數逼近一個指數函數時,首先最重要的是要意識到指數函數與對數函數互為反函數,其次,每個指數函數都可以轉化為自然指數函數的形式:在對復變指數函數f(x) = x求導前,要先用一個簡單的指數函數f(x) = 2來證明複變函數的一種性質。先用上述方程將2 轉化為exp(xln(2)),再用鏈式法則求導。
  • 分享幾個javascript實用函數
    從本文開始小編將定期發布javascript相關的代碼集錦,每次發十個與大家分享,首先是數組篇,也許有人會說,可以用常用的lodash的等庫啊。但是小編覺得,去讀lodash源碼的人並不多吧,所以分享的代碼集錦權當一種學習了,首先開始的是數組篇,基於es6 規範allallEqual找出數組中滿足篩洗條件中的所有元素.any// 找出數組中滿足篩洗條件中的所有元素.
  • Excel函數 最常用的幾個函數怎麼用,一篇文章教會你
    我們學習使用,就是學習了解函數的這種設定,只要了解到函數的設定,了解其每個參數代表的含義,就能快捷高效地使用他們了!那麼今天我教大家使用幾種最常見的函數!SUM函數,AVERAGE函數這兩個函數是求和函數和求平均函數,計算結果分別對應 和 與 平均值 ,他的參數可以是任意單元格,連續單元格可看作一個參數,當計算不同單元格求和時,可以用 , 號隔開(英文狀態下半角符號),如圖:IF函數IF函數是判定函數,這個函數有三個參數,其作用和參數表示的情況如下:判定某個單元格是否滿足某個條件(第一個參數
  • 反函數定理和隱函數定理淺談
    一首先,所謂的「反函數定理和隱函數定理」,其實是一系列定理。比如,是一維的,還是高維的;是有限維的,還是無限維,例如一般的巴拿赫空間上的;是線性空間上的,還是流形上的;是連續的,還是連續可微、高階連續可微、光滑、解析的;是實的,還是復的;是局部的,還是全局的;……不一而足,大家應該能夠舉一反三、觸類旁通才好。
  • JS之函數傳參與深淺拷貝原理
    很多問題看似複雜,沒有章法,事實上卻有著千絲萬縷的聯繫,陳道長此次闡述因為數據類型不同而引發的問題,本文主要探討JS函數參數傳遞規則、淺拷貝、深拷貝的原理。變量類型和存儲首先要明確js中變量的特點,JS變量本身沒有類型,只有值有類型。這句話怎麼理解呢,先看下面這段代碼。
  • LARGE函數和SMALL函數,你會用嗎?
    今天與大家分享兩個實用的最值函數即LARGE函數和SMALL函數。一、函數的基本語法結構LARGE函數用於返回數據集中的第幾個最大值。語法結構=LARGE(查找區域,第幾個最大值)SMALL函數用於返回數據集中的第幾個最小值。
  • 萬能函數Sumproduct超級實用技巧解讀!
    Sumproduct函數是Excel中的數學函數,其功能非常的強大,但是對於具體的用法和技巧,也不能說出個頭緒來……今天,小編帶大家系統的學習Sumproduct函數的具體用法和技巧!一、功能、語法及基礎用法。
  • 2021考研數學大綱一元函數微分學複習重點
    一元函數微分學   考試內容   導數和微分的概念 導數的幾何意義和物理意義 函數的可導性與連續性之間的關係 平面曲線的切線和法線 導數和微分的四則運算 基本初等函數的導數複合函數、反函數、隱函數以及參數方程所確定的函數的微分法高階導數一階微分形式的不變性 微分中值定理 洛必達(L』Hospital)法則 函數單調性的判別
  • excel經典函數組合:index+match!工作中非常實用,案例解析掌握
    課程信息卡課程:《Excel天天訓練營》2.0圖文版章節:第2章-精通函數內容:定位查找(index\match)在excel函數裡面,index+match這一組函數做定位查找是非常實用的。通過index+match這一組函數就可以定位到兩個數據的交叉位置,即查詢結果。如果你沒有學會這些函數,那麼就無法應對大量數據的表格。現在,我們就來用函數公式實現excel自動化辦公。
  • Perl數學函數用法大全
    Perl數學函數用法大全 Perl語言中有多種Perl函數,有Perl進程控制函數,字符串處理函數等,這裡向大家簡單介紹一下Perl數學函數的用法,希望本文的介紹能讓你有所收穫。
  • 高階導數公式匯總
    最近很多人問小編關於某函數高階導數的問題,包括在知乎上也有不少。今天的推文小編匯總一下能夠想到的高階導數公式,還有什麼要補充的歡迎留言補充。
  • 了解一階高通濾波器傳遞函數
    另一種說法是傳遞函數零導致T(s)= 0並且傳遞函數極點導致T(s)→∞;輪詢調查導致系統波特圖幅度響應的斜率下降20dB/decade;零點導致斜率增加20dB/decade;輪詢貢獻-90°的相移,零點貢獻+ 90的相移。一階RC高通電路實現如下: 一階高通濾波器的輸入到輸出行為可以通過以下標準化傳遞函數來描述:
  • 用數形結合的思想求lnx函數導數
    函數導數是從無窮小的定義而來。比方說對函數y=x^2求導。這y』=((x+Δx)^2-x^2)/ Δx=(x^2+2*x*Δx+Δx^2-x^2)/Δx ;  由於Δx是無窮小,那麼高階的Δx^2≈0,  從而,y』=2*x*Δx/Δx=2x。根據同樣的基本定義,我們去求函數y=lnx的導數。