Nice to meet you!css
能來到這裏就表明已經看過上一篇關於並不孤獨的閉包文章了(沒看不能來?固然......能夠來),那麼就不廢話了,先來看下什麼是高階函數html
下面看個簡單的demo:前端
說實在的原本只是個簡單的🌰,不過越寫越興奮,就弄成了個小demo了,你們也能夠copy下去本身添油加醋一下(寫成各類版本),樂呵一下吧,PS:因爲代碼過多佔用文章,將css樣式去掉了,樣式的實現你們隨意發揮就行了node
<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> 複製代碼
js部分jquery
// 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 = new Set(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'); }); }; 複製代碼
按照上面的代碼啪啪啪,就會獲得下面這樣的效果,一塊兒來看下吧ajax
不過依然感謝網易雲音樂提供的API接口,讓咱們聆聽美妙好音樂編程
親們,函數做爲返回值輸出的應用場景那就太多了,這也體現了函數式編程的思想。其實從閉包的例子中咱們就已經看到了關於高階函數的相關內容了,哈哈設計模式
還記得在咱們去判斷數據類型的時候,咱們都是經過Object.prototype.toString來計算的。每一個數據類型之間只是'[object XXX]'不同罷了api
因此在咱們寫類型判斷的時候,通常都是將參數傳入函數中,這裏我簡單寫一下實現,我們先來看看數組
function isType(type) { return function(obj) { return Object.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) { return function() { if (--time === 0) { cb(); } } } // 舉個栗子吧,吃飯的時候,我很能吃,吃了三碗才能吃飽 let eat = after(3, function() { console.log('吃飽了'); }); eat(); eat(); eat(); 複製代碼
上面的eat函數只有執行3次的時候纔會輸出'吃飽了',仍是比較形象的。
這種預置函數也是js中巧妙的裝飾者模式的實現,裝飾者模式在實際開發中也很是有用,再之後的歲月裏我也會好好研究以後分享給你們的
好了,不要停,不要停,再來看一個栗子
// 這裏咱們建立了一個單例模式 let single = function (fn) { let ret; return function () { 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'); } return true; }); let renders = function () { console.log('渲染'); bindEvent(); } renders(); renders(); renders(); 複製代碼
這個高階函數的栗子,能夠說一石二鳥啊,既把函數當作參數傳遞了,又把函數當返回值輸出了。
單例模式也是一種很是實用的設計模式,在之後的文章中也會針對這些設計模式去分析的,敬請期待,哈哈,下面再看看高階函數還有哪些用途
柯里化又稱部分求值,柯里化函數會接收一些參數,而後不會當即求值,而是繼續返回一個新函數,將傳入的參數經過閉包的形式保存,等到被真正求值的時候,再一次性把全部傳入的參數進行求值
還能闡述的更簡單嗎?在一個函數中填充幾個參數,而後再返回一個新函數,最後進行求值,沒了,是否是說的簡單了
說的再簡單都不如幾行代碼演示的清楚明白
// 普通函數 function add(x,y){ return x + y; } add(3,4); // 7 // 實現了柯里化的函數 // 接收參數,返回新函數,把參數傳給新函數使用,最後求值 let add = function(x){ return function(y){ return x + y; } }; add(3)(4); // 7 複製代碼
以上代碼很是簡單,只是起個引導的做用。下面咱們來寫一個通用的柯里化函數
function curry(fn) { let slice = Array.prototype.slice, // 將slice緩存起來 args = slice.call(arguments, 1); // 這裏將arguments轉成數組並保存 return function() { // 將新舊的參數拼接起來 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); return function() { 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方法 return function() { 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; return function() { return Function.prototype.call.apply(self, arguments); } }; 複製代碼
實現方式大體相同,你們也能夠寫一下試試,動動手,活動一下筋骨
下面再說一下函數節流,咱們都知道在onresize、onscroll和mousemove,上傳文件這樣的場景下,函數會被頻繁的觸發,這樣很消耗性能,瀏覽器也會吃不消的
因而你們開始研究一種高級的方法,那就是控制函數被觸發的頻率,也就是函數節流了。簡單說一下原理,利用setTimeout在必定的時間內,函數只觸發一次,這樣大大下降了頻率問題
函數節流的實現也多種多樣,這裏咱們實現你們經常使用的吧
function throttle (fn, wait) { let _fn = fn, // 保存須要被延遲的函數引用 timer, flags = true; // 是否首次調用 return function() { let args = arguments, self = this; if (flags) { // 若是是第一次調用不用延遲,直接執行便可 _fn.apply(self, args); flags = false; return flags; } // 若是定時器還在,說明上一次還沒執行完,不往下執行 if (timer) return false; 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); } } return function() { 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); } else if (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); } } else if (window.attachEvent) { addEvent = function(ele, type, fn) { ele.attachEvent('on' + type, function() { fn.call(ele) }); } } addEvent(ele, type, fn); }; 複製代碼
上面的addEvent函數仍是個普通函數,仍是有分支判斷。不過當第一次進入分支條件後,在內部就會重寫了addEvent函數
下次再進入addEvent函數的時候,函數裏就不存在條件判斷了
節目不早,時間恰好,又到了該要說再見的時候了,來一個結束語吧
我勒個去,竟然羅列了這麼多東西,你們看的也很辛苦了,早睡早起,好好休息吧!
這兩篇也是爲了給觀察者模式起個頭,以後會繼續寫文章來和你們好好分享的,謝謝各位小主,阿哥的觀看了!哈哈