JavaScript中的高阶函数

更新日期: 2020-04-27阅读: 1.9k标签: 函数

前言

在 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 的隐式转换特性,最终拿到我们想要的结果


链接: https://fly63.com/article/detial/9191

JavaScript 函数式编程

我理解的 JavaScript 函数式编程,都认为属于函数式编程的范畴,只要他们是以函数作为主要载体的。

Js函数式编程,给你的代码增加一点点函数式编程的特性

给你的代码增加一点点函数式编程的特性,最近我对函数式编程非常感兴趣。这个概念让我着迷:应用数学来增强抽象性和强制纯粹性,以避免副作用,并实现代码的良好可复用性。同时,函数式编程非常复杂。

让我们来创建一个JavaScript Wait函数

Async/await以及它底层promises的应用正在猛烈地冲击着JS的世界。在大多数客户端和JS服务端平台的支持下,回调编程已经成为过去的事情。当然,基于回调的编程很丑陋的。

JavaScript函数创建的细节

如果你曾经了解或编写过JavaScript,你可能已经注意到定义函数的方法有两种。即便是对编程语言有更多经验的人也很难理解这些差异。在这篇博客的第一部分,我们将深入探讨函数声明和函数表达式之间的差异。

编写小而美函数的艺术

随着软件应用的复杂度不断上升,为了确保应用稳定且易拓展,代码质量就变的越来越重要。不幸的是,包括我在内的几乎每个开发者在职业生涯中都会面对质量很差的代码。这些代码通常有以下特征:

javascript回调函数的理解和使用方法(callback)

在js开发中,程序代码是从上而下一条线执行的,但有时候我们需要等待一个操作结束后,再进行下一步操作,这个时候就需要用到回调函数。 在js中,函数也是对象,确切地说:函数是用Function()构造函数创建的Function对象。

js调用函数的几种方法_ES5/ES6的函数调用方式

这篇文章主要介绍ES5中函数的4种调用,在ES5中函数内容的this指向和调用方法有关。以及ES6中函数的调用,使用箭头函数,其中箭头函数的this是和定义时有关和调用无关。

JavaScript中函数的三种定义方法

函数的三种定义方法分别是:函数定义语句、函数直接量表达式和Function()构造函数的方法,下面依次介绍这几种方法具体怎么实现,在实际编程中,Function()构造函数很少用到,前两中定义方法使用比较普遍。

js在excel的编写_excel支持使用JavaScript自定义函数编写

微软 称excel就实现面向开发者的功能,也就是说我们不仅可以全新定义的公式,还可以重新定义excel的内置函数,现在Excel自定义函数增加了使用 JavaScript 编写的支持,下面就简单介绍下如何使用js来编写excel自定义函数。

js中的立即执行函数的写法,立即执行函数作用是什么?

这篇文章主要讲解:js立即执行函数是什么?js使用立即执行函数有什么作用呢?js立即执行函数的写法有哪些?

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!