关闭

JavaScript中的高阶函数

时间: 2020-04-27阅读: 347标签: 函数

前言

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


站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/9191

关闭

eval到底哪里不好?

为什么要少用eval?eval是 js 中一个强大的方法。都说eval == evil等于true,这篇文章将研讨eval的几个缺点和使用注意事项。

函数式编程术语及示例

函数式编程有许多优势,由此越来越受欢迎。然而每个编程范式 (paradigm) 都有自己唯一的术语,函数式编程也不例外。我们提供一张术语表,希望使你学习函数式编程变得容易些。

浅析js中的纯函数、高阶函数、记忆函数、偏函数

上周分享文档中遇到几个关键名称,纯函数、高阶函数、记忆函数、偏函数....,这里做一下解析与举例,纯函数是函数式编程中非常重要的一个概念,简单来说,就是一个函数的返回结果只依赖于它的参数

JS 中构造函数和普通函数的区别

构造函数也是一个普通函数,创建方式和普通函数一样,但构造函数习惯上首字母大写、构造函数和普通函数的区别在于:调用方式不一样。作用也不一样(构造函数用来新建实例对象)

JS异常函数之箭头函数

在JS中,箭头函数可以像普通函数一样以多种方式使用。但是,它们一般用于需要匿名函数表达式,例如回调函数。下面示例显示举例箭头函数作为回调函数,尤其是对于map(), filter(), reduce(), sort()等数组方法。

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

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

js函数式编程术语总结

函数式编程(FP)有许多优点,它也越来越流行了。然而,每个编程范式都有自己独特的术语,函数式编程也不例外。通过提供的这张术语表,希望使你学习函数式编程变得容易些。

js查找和筛选的几种方式

find() 方法返回通过测试(函数内判断)的数组的第一个元素的值。find() 方法为数组中的每个元素都调用一次函数执行;findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。

CSS之calc()

calc() 函数支持任意CSS长度单位的混合计算,遵循标准数学运算优先级规则,可以动态计算长度值。注意,calc()函数内部的运算符两侧各加一个空白符,否则会产生解析错误。calc()使用的难点在于百分比

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

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

点击更多...

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