JavaScript中的高階函數

前言

在 JavaScript 的學習過程當中,咱們可能或多或少地接觸太高階函數。那麼,咱們本身對此是否有一個明確的定義,或者說很熟練的掌握這些用法呢面試

簡單來講,高階函數是一個函數,它接收函數做爲參數或將函數做爲輸出返回

看到這樣的概念,在你的腦海中會出現哪些函數呢數組

其實,像咱們常常會使用到的一些數組方法,好比:map、filter 等等都是高階函數的範疇閉包

固然,這些 JavaScript 內置的方法不在本文的討論範圍以內,下面會列舉一些在咱們實際開發或者面試過程當中可能會遇到的函數高階用法app

防抖

任務頻繁觸發的狀況下,只有任務觸發的間隔超過指定間隔的時候,任務纔會執行

實現方式就是若是任務觸發的頻率比咱們設定的時間要小,那麼咱們就清除掉上一個任務從新計時函數

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
}    

 

惰性函數修改版:this

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
}

 

經過上面修改以後,當咱們在第一次調用這個函數的時候就會去判斷當前的環境,進而將函數優化簡寫,不須要再進行後續的判斷spa

好比,上述代碼中的 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 的隱式轉換特性,最終拿到咱們想要的結果

相關文章
相關標籤/搜索