前言 在 JavaScript 的學習過程中,我們可能或多或少地接觸過高階函數。那麼,我們自己對此是否有一個明確的定義,或者說很熟練的掌握這些用法呢 簡單來說,高階函數是一個函數,它接收函數作為參數或將函數作為輸出返回 看到這樣的概念,在你的腦海中會出現哪些函數呢 其實,像我們經常會使用到的一些數組 ...
前言
在 JavaScript 的學習過程中,我們可能或多或少地接觸過高階函數。那麼,我們自己對此是否有一個明確的定義,或者說很熟練的掌握這些用法呢
簡單來說,高階函數是一個函數,它接收函數作為參數或將函數作為輸出返回
看到這樣的概念,在你的腦海中會出現哪些函數呢
其實,像我們經常會使用到的一些數組方法,比如:map、filter 等等都是高階函數的範疇
當然,這些 JavaScript 內置的方法不在本文的討論範圍之內,下麵會列舉一些在我們實際開發或者面試過程中可能會遇到的函數高階用法
防抖
任務頻繁觸發的情況下,只有任務觸發的間隔超過指定間隔的時候,任務才會執行
實現方式就是如果任務觸發的頻率比我們設定的時間要小,那麼我們就清除掉上一個任務重新計時
function debounce(fn, interval) { let timer = null return function() { // 如果用戶在設定的時間內再次觸發,就清除掉 clearTimeout(timer) timer = setTimeout(() => { fn.apply(this, arguments) }, interval); } }
節流
指定時間間隔內只會執行一次任務
function throttle(fn, interval) { let timer, firstTime = true // 是否是第一次執行 return function () { let _this = this if (firstTime) { fn.apply(_this, arguments) // 第一次不需要延遲執行 return firstTime = false } if (timer) { // 如果定時器還在 說明前一次還沒有執行完 return false } timer = setTimeout(() => { clearTimeout(timer) timer = null fn.apply(_this, arguments) }, interval || 500); } } // 不考慮定時器的情況 直接加一個節流閥 function throttle(fn, interval) { let canRun = true //節流閥 return function() { if(!canRun) { return } canRun = false setTimeout(() => { fn.apply(this, arguments) canRun = true }, interval); } }
無論是防抖還是節流,我們都可以使用下麵的這種方式去驗證一下
window.onresize = throttle(function () { console.log(1) }, 1000)
通過上面兩種方式的實現,相信小伙伴們也都瞭解了所謂防抖和節流我們都是藉助 setTimeOut 來實現,不同的地方就是 清除定時器的位置
惰性函數
當我們需要重覆使用一個邏輯的時候,優化邏輯判斷,提高JavaScript
性能原理:同名函數覆蓋
function createXHR() { var xhr try { xhr = new XMLHttpRequest() } catch (e) { handleErr(e) try { xhr = new ActiveXObject('Msxml2.XMLHTTP') } catch (e) { try { xhr = new ActiveXObject('Microsoft.XMLHTTP') } catch (e) { xhr = null } } } return xhr } function handleErr(e) { // do sth }
惰性函數修改版:
function createXHR() { var xhr if(typeof XMLHttpRequest !== 'undefined') { xhr = new XMLHttpRequest() } createXHR = function() { return new XMLHttpRequest() } else { try { xhr = new ActiveXObject('Msxml2.XMLHTTP') createXHR = function() { return new ActiveXObject('Msxml2.XMLHTTP') } } catch(e) { try { xhr = new ActiveXObject('Microsoft.XMLHTTP') createXHR = function() { return new ActiveXObject('Microsoft.XMLHTTP') } } catch(e) { createXHR = function() { return null } } } } return xhr }
通過上面修改之後,當我們在第一次調用這個函數的時候就會去判斷當前的環境,進而將函數優化簡寫,不需要再進行後續的判斷
比如,上述代碼中的 XMLHTTPRequest 如果是存在的,那麼當我們第二次調用這個函數的時候已經是這樣了
function createXHR() { return new XMLHttpRequest() }
使用場景:
-
頻繁使用同一判斷邏輯
-
只需要判斷一次,後續使用環境穩定
級聯函數
其實就是鏈式調用,所以原理也就很簡單:在每個方法中將對象本身 return 出去
假設我們有一個 Person 模板
function Person() {} // 添加幾個方法 Person.prototype = { setName: function (name) { this.name = name return this // }, setAge: function (age) { this.age = age return this }, setSex: function (sex) { this.sex = sex }, } // 別忘了重新指定一下構造函數 Person.constructor = Person let person = new Person() // 這樣看起來做了很多重覆的事情,稍微修改一下,在每個方法中將 this return 出來就可以達到 鏈式調用的效果 person.setName('游蕩de蝌蚪') person.setAge(18) person.setSex('male') // 修改之後 person.setName('游蕩de蝌蚪').setAge(18).setSex('male') console.log(person)
優點:簡化了函數調用的步驟,我們不需要再寫一些重覆的東西
缺點:占用了函數的返回值
柯里化
收集參數,延後執行,也可以稱之為部分求值
function add(a, b) { return a + b } // 簡單的通用封裝 function curry(fn) { let args = Array.prototype.slice.call(arguments, 1) return function() { let _args = Array.prototype.slice.call(arguments) let final = args.concat(_args) return fn.apply(null, final) } } // 對函數 add 柯里化 let adder = curry(add) adder(1, 2) // 或者 let adder = curry(add, 1)(2) let adder = curry(add)(1, 2) 一個典型的通用型 curry 封裝 Function.prototype.mybind = function(fn) { let args = Array.prototype.slice(arguments, 1) let _this = this return function() { let _args = Array.prototype.slice(arguments) let final = args.concat(_args) return _this.apply(fn, final) } }
通過 curry 函數的這種模式,我們就能實現一個簡單的 bind
Function.prototype.mybind = function(fn) { let _this = this return function() { return _this.apply(fn, arguments) } }
函數柯里化也是我們在面試過程中可能會經常碰到的問題,比如:
// 編寫一個 add 函數,實現以下功能 add(1)(2)(3) // 6 add(1)(2, 3)(4) //10 add(1, 2)(3) (4, 5) // 15 function add() { let args = Array.prototype.slice.call(arguments) let adder = function() { // 利用閉包的特性保存 args 並且收集參數 args = args.concat(Array.prototype.slice.call(arguments)) return adder } // 利用 toString 隱式轉換的的特性返回最終計算的值 adder.toString = function() { return args.reduce((a, b) => { return a + b }) } return adder } add(1)(2)(3) // 6 add(1)(2, 3)(4) // 10 add(1, 2)(3)(4, 5) // 15 // 當然,我們也可以藉助ES6的方法簡化這個函數 function add1(...args) { let adder = (..._args) => { args = [...args, ..._args] return adder } adder.toString = () => args.reduce((a, b) => a + b) return adder }
想要實現上面函數的效果,我覺得有兩點是我們必須理解和掌握的:
-
閉包,使用閉包的特性去保存和收集我們需要的參數
-
利用 toString 的隱式轉換特性,最終拿到我們想要的結果