Higher-order functions in JavaScript

Posted May 27, 20205 min read

Foreword

In the learning process of JavaScript, we may have come into contact with higher-order functions more or less. So, do we have a clear definition of this, or are we skilled in mastering these usages?

If there are any mistakes or mistakes in the article, please see the little friends for advice, thank you in advance

Below

In simple terms, a higher-order function is a function that takes a function as a parameter or returns a function as an output

Seeing such a concept, what functions will appear in your mind?

In fact, some of the array methods we often use, such as:map, filter, etc. are all categories of higher-order functions

Of course, these JavaScript built-in methods are beyond the scope of this article. The following will list some of the higher-level usages of functions that we may encounter during actual development or interview

Anti-shake

When the task is triggered frequently, the task will only be executed when the interval between task triggers exceeds the specified interval

The implementation method is that if the frequency of the task trigger is less than the time we set, then we will clear the previous task and retime

function debounce(fn, interval) {
    let timer = null
    return function() {
        //If the user triggers again within the set time, it is cleared
        clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(this, arguments)
        }, interval);
    }
}

Throttling

The task will only be executed once in the specified time interval

function throttle(fn, interval) {
    let timer,
        firstTime = true //Whether it is the first time to execute
    return function() {
        let _this = this
        if(firstTime) {
            fn.apply(_this, arguments) //The first time without delay execution
            return firstTime = false
        }
        if(timer) {//If the timer is still on, it means that the previous execution has not been completed
            return false
        }
        timer = setTimeout(() => {
            clearTimeout(timer)
            timer = null
            fn.apply(_this, arguments)
        }, interval || 500);
    }
}

//Add a throttle directly regardless of the timer
function throttle(fn, interval) {
    let canRun = true //throttle valve
    return function() {
        if(! canRun) {
            return
        }
        canRun = false
        setTimeout(() => {
            fn.apply(this, arguments)
            canRun = true
        }, interval);
    }
}

Whether it is anti-shake or throttle, we can use the following method to verify

window.onresize = throttle(function() {
    console.log(1)
}, 1000)

Through the implementation of the above two methods, I believe that the small partners have also understood the so-called anti-shake and throttling. We all use setTimeOut to achieve, the difference is clear the position of the timer

Lazy function

When we need to reuse a logic, optimize the logic judgment and improve the performance of JavaScript

Principle:Overwrite function with the same name

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
}

Modified version of lazy function:

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
}

After the above modification, when we call this function for the first time, we will judge the current environment, and then optimize the function shorthand, no need to make subsequent judgments

For example, if the "XMLHTTPRequest" in the above code exists, then when we call this function for the second time, it is already like this

function createXHR() {
    return new XMLHttpRequest()
}

scenes to be used:

  • Frequent use of the same judgment logic
  • Only need to judge once, the subsequent use environment is stable

Cascading functions

In fact, it is a chain call, so the principle is very simple:in each method, the object itself return out

Suppose we have a Person template

function Person() {}
//add a few methods
Person.prototype = {
    setName:function(name) {
        this.name = name
        return this //
    },
    setAge:function(age) {
        this.age = age
        return this
    },
    setSex:function(sex) {
        this.sex = sex
    },
}
//Don't forget to re-specify the constructor
Person.constructor = Person
let person = new Person()
//This seems to do a lot of repetitive things, a little modification, in this method, return this out to achieve the effect of chain call
person.setName('Wandering de tadpole')
person.setAge(18)
person.setSex('male')
//after modification
person.setName('Wandering tadpole'). setAge(18) .setSex('male')
console.log(person)

Advantages:Simplify the steps of function call, we don't need to write something repetitive

Disadvantages:occupied the return value of the function

Currying

Collect parameters and postpone execution, also called partial evaluation

function add(a, b) {
    return a + b
}

//Simple universal package
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)
    }
}

//Currying of the function add
let adder = curry(add)
adder(1, 2)
//or
let adder = curry(add, 1)(2)
let adder = curry(add)(1, 2)

A typical general-purpose curry package

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)
    }
}

Through this pattern of the curry function, we can achieve a simple bind

Function.prototype.mybind = function(fn) {
    let _this = this
    return function() {
        return _this.apply(fn, arguments)
    }
}

Function currying is also a problem we may often encounter during the interview process, such as:

//Write an add function to achieve the following functions
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() {
        //Save the args and collect the parameters using the characteristics of the closure
        args = args.concat(Array.prototype.slice.call(arguments))
        return adder
    }
    //Use the toString implicit conversion feature to return the final calculated value
    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

//Of course, we can also use ES6 to simplify this function
function add1(... args) {
    let adder =(..._ args) => {
        args = [... args, ..._ args]
        return adder
    }
    adder.toString =() => args.reduce((a, b) => a + b)
    return adder
}

To achieve the effect of the above function, I think there are two points that we must understand and master:

  • Closures, use the characteristics of closures to save and collect the parameters we need
  • Use the implicit conversion feature of toString to finally get the result we want

Postscript

After reading the above examples of high-level usage of functions, what is your first impression?

Closure! As a qualified front end, closures are a knowledge point that we must conquer

Finally, I recommend a wave of front-end learning, and share some front-end problems and interesting things from time to time. Welcome star attention Portal