关于JavaScript中高阶函数的魅力详解

前言

一个函数就可以接收另一个函数作为参数,简言之,函数的参数能够接收别的函数,这种函数就称之为高阶函数

JavaScript 的高阶函数跟 Swift 的高阶函数类似

常见的高阶函数有: Map、Reduce、Filter、Sort

高阶函数是指至少满足下列条件之一的函数

1:函数可以作为参数被传递

2:函数可以作为返回值输出

JavaScript语言中的函数显然的是满足了高阶函数的条件,下面我们一起来探寻JavaScript种高阶函数的魅力。

高阶函数实现AOP

AOP(面向切面编程)的主要作用就是把一些和核心业务逻辑模块无关的功能抽取出来,然后再通过“动态织入”的方式掺到业务模块种。这些功能一般包括日志统计,安全控制,异常处理等。AOP是Java Spring架构的核心。下面我们就来探索一下再Javascript种如何实现AOP

在JavaScript种实现AOP,都是指把一个函数“动态织入”到另外一个函数中,具体实现的技术有很多,我们使用Function.prototype来做到这一点。代码如下

/**
* 织入执行前函数
* @param {*} fn
*/
Function.prototype.aopBefore = function(fn){
 console.log(this)
 // 第一步:保存原函数的引用
 const _this = this
 // 第四步:返回包括原函数和新函数的“代理”函数
 return function() {
 // 第二步:执行新函数,修正this
 fn.apply(this, arguments)
 // 第三步 执行原函数
 return _this.apply(this, arguments)
 }
}
/**
* 织入执行后函数
* @param {*} fn
*/
Function.prototype.aopAfter = function (fn) {
 const _this = this
 return function () {
 let current = _this.apply(this,arguments)// 先保存原函数
 fn.apply(this, arguments) // 先执行新函数
 return current
 }
}
/**
* 使用函数
*/
let aopFunc = function() {
 console.log('aop')
}
// 注册切面
aopFunc = aopFunc.aopBefore(() => {
 console.log('aop before')
}).aopAfter(() => {
 console.log('aop after')
})
// 真正调用
aopFunc()

currying(柯里化)

关于curring我们首先要聊的是什么是函数柯里化。

curring又称部分求值。一个curring的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数中被真正的需要求值的时候,之前传入的所有参数被一次性用于求值。

生硬的看概念不太好理解,我们来看接下来的例子

我们需要一个函数来计算一年12个月的消费,在每个月月末的时候我们都要计算消费了多少钱。正常代码如下

// 未柯里化的函数计算开销
let totalCost = 0
const cost = function(amount, mounth = '') {
 console.log(`第${mounth}月的花销是${amount}`)
 totalCost += amount
 console.log(`当前总共消费:${totalCost}`)
}
cost(1000, 1) // 第1个月的花销
cost(2000, 2) // 第2个月的花销
// ...
cost(3000, 12) // 第12个月的花销

总结一下不难发现,如果我们要计算一年的总消费,没必要计算12次。只需要在年底执行一次计算就行,接下来我们对这个函数进行部分柯里化的函数帮助我们理解

// 部分柯里化完的函数
const curringPartCost = (function() {
 // 参数列表
 let args = []
 return function (){
 /**
 * 区分计算求值的情况
 * 有参数的情况下进行暂存
 * 无参数的情况下进行计算
 */
 if (arguments.length === 0) {
  let totalCost = 0
  args.forEach(item => {
  totalCost += item[0]
  })
  console.log(`共消费:${totalCost}`)
  return totalCost
 } else {
  // argumens并不是数组,是一个类数组对象
  let currentArgs = Array.from(arguments)
  args.push(currentArgs)
  console.log(`暂存${arguments[1] ? arguments[1] : '' }月,金额${arguments[0]}`)
 }
 }
})()
curringPartCost(1000,1)
curringPartCost(100,2)
curringPartCost()

接下来我们编写一个通用的curring, 以及一个即将被curring的函数。代码如下

// 通用curring函数
const curring = function(fn) {
 let args = []
 return function () {
 if (arguments.length === 0) {
  console.log('curring完毕进行计算总值')
  return fn.apply(this, args)
 } else {
  let currentArgs = Array.from(arguments)[0]
  console.log(`暂存${arguments[1] ? arguments[1] : '' }月,金额${arguments[0]}`)
  args.push(currentArgs)
  // 返回正被执行的 Function 对象,也就是所指定的 Function 对象的正文,这有利于匿名函数的递归或者保证函数的封装性
  return arguments.callee
 }
 }
}
// 求值函数
let costCurring = (function() {
 let totalCost = 0
 return function () {
 for (let i = 0; i < arguments.length; i++) {
  totalCost += arguments[i]
 }
 console.log(`共消费:${totalCost}`)
 return totalCost
 }
})()
// 执行curring化
costCurring = curring(costCurring)
costCurring(2000, 1)
costCurring(2000, 2)
costCurring(9000, 12)
costCurring()

函数节流

JavaScript中的大多数函数都是用户主动触发的,一般情况下是没有性能问题,但是在一些特殊的情况下不是由用户直接控制。容易大量的调用引起性能问题。毕竟DOM操作的代价是非常昂贵的。下面将列举一些这样的场景:

  • window.resise事件。
  • mouse, input等事件。
  • 上传进度
  • ...

下面通过高阶函数的方式我们来实现函数节流

/**
* 节流函数
* @param {*} fn
* @param {*} interval
*/
const throttle = function (fn, interval = 500) {
 let timer = null, // 计时器
  isFirst = true // 是否是第一次调用
 return function () {
 let args = arguments, _me = this
 // 首次调用直接放行
 if (isFirst) {
  fn.apply(_me, args)
  return isFirst = false
 }
 // 存在计时器就拦截
 if (timer) {
  return false
 }
 // 设置timer
 timer = setTimeout(function (){
 console.log(timer)
 window.clearTimeout(timer)
 timer = null
 fn.apply(_me, args)
 }, interval)
 }
}
// 使用节流
window.onresize = throttle(function() {
 console.log('throttle')
},600)

分时函数

节流函数为我们提供了一种限制函数被频繁调用的解决方案。下面我们将遇到另外一个问题,某些函数是用户主动调用的,但是由于一些客观的原因,这些操作会严重的影响页面性能,此时我们需要采用另外的方式去解决。

如果我们需要在短时间内才页面中插入大量的DOM节点,那显然会让浏览器吃不消。可能会引起浏览器的假死,所以我们需要进行分时函数,分批插入。

/**
* 分时函数
* @param {*创建节点需要的数据} list
* @param {*创建节点逻辑函数} fn
* @param {*每一批节点的数量} count
*/
const timeChunk = function(list, fn, count = 1){
 let insertList = [], // 需要临时插入的数据
  timer = null // 计时器
 const start = function(){
 // 对执行函数逐个进行调用
 for (let i = 0; i < Math.min(count, list.length); i++) {
  insertList = list.shift()
  fn(insertList)
 }
 }
 return function(){
 timer = setInterval(() => {
  if (list.length === 0) {
  return window.clearInterval(timer)
  }
  start()
 },200)
 }
}
// 分时函数测试
const arr = []
for (let i = 0; i < 94; i++) {
 arr.push(i)
}
const renderList = timeChunk(arr, function(data){
 let div =document.createElement('div')
 div.innerHTML = data + 1
 document.body.appendChild(div)
}, 20)
renderList()

惰性加载函数

在Web开发中,因为一些浏览器中的差异,一些嗅探工作总是不可避免的。

因为浏览器的差异性,我们要常常做各种各样的兼容,举一个非常简单常用的例子:在各个浏览器中都能够通用的事件绑定函数。
常见的写法是这样的:

// 常用的事件兼容
const addEvent = function(el, type, handler) {
 if (window.addEventListener) {
 return el.addEventListener(type, handler, false)
 }
 // for IE
 if (window.attachEvent) {
 return el.attachEvent(`on${type}`, handler)
 }
}
复制代码这个函数存在一个缺点,它每次执行的时候都会去执行if条件分支。虽然开销不大,但是这明显是多余的,下面我们优化一下, 提前一下嗅探的过程:
const addEventOptimization = (function() {
 if (window.addEventListener) {
 return (el, type, handler) => {
  el.addEventListener(type, handler, false)
 }
 }
 // for IE
 if (window.attachEvent) {
 return (el, type, handler) => {
  el.attachEvent(`on${type}`, handler)
 }
 }
})()

这样我们就可以在代码加载之前进行一次嗅探,然后返回一个函数。但是如果我们把它放在公共库中不去使用,这就有点多余了。下面我们使用惰性函数去解决这个问题:

// 惰性加载函数
let addEventLazy = function(el, type, handler) {
 if (window.addEventListener) {
 // 一旦进入分支,便在函数内部修改函数的实现
 addEventLazy = function(el, type, handler) {
  el.addEventListener(type, handler, false)
 }
 } else if (window.attachEvent) {
 addEventLazy = function(el, type, handler) {
  el.attachEvent(`on${type}`, handler)
 }
 }
 addEventLazy(el, type, handler)
}
addEventLazy(document.getElementById('eventLazy'), 'click', function() {
 console.log('lazy ')
})

一旦进入分支,便在函数内部修改函数的实现,重写之后函数就是我们期望的函数,在下一次进入函数的时候就不再存在条件分支语句。

总结

该文章主要是读《Javascript设计模式》的总结。

代码地址

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • javascript之典型高阶函数应用介绍

    缘由 虽然以前也使用过javascript语言,但终究是为了配合后端写的一些零零散散的"代码段",更不能说是javascript项目了.很荣幸的是上个月刚到公司正好碰上项目开始推倒重写,我们team从头开始做架构和实现,目的很清楚,为了改进和超越前面的版本.这是个真正意义上的javascript"项目",当然服务端不是我们team来负责啦.这也是我真正开始全职使用javascript来编程.由于之前在学校对形式化方法这门课程比较感兴趣,而javascript又是函数

  • Javascript 是你的高阶函数(高级应用)

    在通常的编程语言中,函数的参数只能是基本类型或者对象引用,返回值也只是基本数据类型或对象引用.但在Javascript中函数作为一等公民,既可以当做参数传递,也可以被当做返回值返回.所谓高阶函数就是可以把函数作为参数,或者是将函数作为返回值的函数.这两种情形在实际开发中有很多应用场景,本文是我在工作学习中遇到的几种应用场景的总结. 回调函数 代码复用是衡量一个应用程序的重要标准之一.通过将变化的业务逻辑抽离封装在回调函数中能够有效的提高代码复用率.比如ES5中为数组增加的forEach方法,遍历

  • JavaScript高阶函数_动力节点Java学院整理

    高阶函数英文叫Higher-order function.那么什么是高阶函数? JavaScript的函数其实都指向某个变量.既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数. 一个最简单的高阶函数: function add(x, y, f) { return f(x) + f(y); } 当我们调用add(-5, 6, Math.abs)时,参数x,y和f分别接收-5,6和函数Math.abs,根据函数定义,我们可以推导计算过程为

  • Javascript中的高阶函数介绍

    这是一个有趣的东西,这或许也在说明Javascript对象的强大.我们要做的就是在上一篇说到的那样,输出一个Hello,World,而输入的东西是print('Hello')('World'),而这就是所谓的高阶函数. 高阶函数 高阶看上去就像是一种先进的编程技术的一个深奥术语,一开始我看到的时候我也这样认为的. Javascript的高阶函数 然而,高阶函数只是将函数作为参数或返回值的函数.以上面的Hello,World作为一个简单的例子. 复制代码 代码如下: var Moqi = func

  • Javascript 高阶函数使用介绍

    高阶函数(higher-order function)-如果一个函数接收的参数为或返回的值为函数,那么我们可以将这个函数称为高阶函数.众所周知,JavaScript是一种弱类型的语言:JavaScript的函数既不对输入的参数,也不对函数的输出值作强定义和类型检查,那么函数可以成为参数,也可以成为输出值,这就体现了JavaScript对高阶函数的原生支持. 一.参数为函数的高阶函数: function funcTest(f){ //简易判断一下实参是否为函数 if((typeof f)=="fu

  • JS高阶函数原理与用法实例分析

    本文实例讲述了JS高阶函数原理与用法.分享给大家供大家参考,具体如下: 如果您正在学习JavaScript,那么您必须遇到高阶函数这个术语.这听起来复杂,其实不然. 使JavaScript适合函数式编程的原因是它接受高阶函数. 高阶函数在JavaScript中广泛使用.如果你已经用JavaScript编程了一段时间,你可能已经使用它们甚至不知道. 要完全理解这个概念,首先必须了解函数式编程是什么一等函数(first-Class Function)以及的概念. 函数式编程 在大多数简单的术语中,函

  • javascript之典型高阶函数应用介绍二

    前言 在前一篇文章javascript之典型高阶函数中主要实现了几个典型的functional函数.文章最后也提出了疑问,为啥那样的实现与F#之类的函数式语言"不太一样"呢?今天来试试更"函数式"的实现. 另一种实现 同样地,尝试对之前实现的函数做一些改动,把for循环去掉.如何去掉呢?这里先要引入一个集合的归纳法定义: 一个集合要么是空集,要么是一个数与一个集合组成的数对从定义可以看到,每一个集合都可以看作为一个数和一个集合的对.例如:{1,2,4,5} 可以认为

  • 关于JavaScript中高阶函数的魅力详解

    前言 一个函数就可以接收另一个函数作为参数,简言之,函数的参数能够接收别的函数,这种函数就称之为高阶函数 JavaScript 的高阶函数跟 Swift 的高阶函数类似 常见的高阶函数有: Map.Reduce.Filter.Sort 高阶函数是指至少满足下列条件之一的函数 1:函数可以作为参数被传递 2:函数可以作为返回值输出 JavaScript语言中的函数显然的是满足了高阶函数的条件,下面我们一起来探寻JavaScript种高阶函数的魅力. 高阶函数实现AOP AOP(面向切面编程)的主要

  • python高级特性和高阶函数及使用详解

    python高级特性 1.集合的推导式 •列表推导式,使用一句表达式构造一个新列表,可包含过滤.转换等操作. 语法:[exp for item in collection if codition] if codition - 可选 •字典推导式,使用一句表达式构造一个新列表,可包含过滤.转换等操作. 语法:{key_exp:value_exp for item in collection if codition} •集合推导式 语法:{exp for item in collection if

  • 8个JavaScript中高阶函数的运用分享

    目录 函数作为参数传递 函数作为返回值 函数柯里化 函数组合 面向切面编程 函数式编程 闭包 复杂的例子 函数作为参数传递 高阶函数可以接受一个或多个函数作为参数,这些函数可以在高阶函数中被调用,实现某种特定的功能 function operation(num1, num2, callback) { let result = callback(num1, num2); console.log(result); } function add(num1, num2) { return num1 +

  • JavaScript装饰器函数(Decorator)实例详解

    本文实例讲述了JavaScript装饰器函数(Decorator).分享给大家供大家参考,具体如下: 装饰器函数(Decorator)用于给对象在运行期间动态的增加某个功能,职责等.相较通过继承的方式来扩充对象的功能,装饰器显得更加灵活,首先,我们可以动态给对象选定某个装饰器,而不用hardcore继承对象来实现某个功能点.其次:继承的方式可能会导致子类繁多,仅仅为了增加某一个单一的功能点,显得有些多余了. 下面给出几个常用的装饰器函数示例,相关代码请查看github. 1 动态添加onload

  • Vue数组响应式操作及高阶函数使用代码详解

    数组的响应式操作 //this.letters.push('123');//在末尾添加一个元素 //this.letters.pop();//从末尾删除一个元素 //this.letters.unshift('111');//在开端添加一个元素 //this.letters.shift();//从开端删除一个元素 //this.letters.splice(1,2);//从下标为1的元素开始删除两个元素 //this.letters.splice(1,2,'777','888');//从下标为1

  • 详解JavaScript 高阶函数

    高阶函数简介 高阶函数 的英文名叫 Higher-Order Function ,是 函数式编程 中的一种.他的表现形式往往是通过把函数作为参数传入另一个函数,或者将函数作为另一个函数的返回值返回.在实际开发业务中, 高阶函数往往可以抽象我们的代码 ,将我们的命令式编程转换为复用性更高级的函数式编程,从而 提升我们的代码质量 . 下面拿3个面试中常问的高阶函数举例子,希望看完以后能够提升大家对JS的理解,提高我们的代码质量. chat is cheap,show you my code~ Arr

  • 详解Python中高阶函数(map,filter,reduce,sorted)的使用

    目录 什么是高阶函数 自定义一个高阶函数 常用的内置高阶函数 map函数 参数说明 功能 实例 filter函数 功能 实例 reduce函数 功能 实例 sorted函数 参数说明 功能 实例 sort和sorted 总结 什么是高阶函数 高阶函数就是能够把函数当成参数传递的函数就是高阶函数,换句话说如果一个函数的参数是函数,那么这个函数就是一个高阶函数. 高阶函数可以是你使用def关键字自定义的函数,也有Python系统自带的内置高阶函数. 自定义一个高阶函数 我们下面的例子中,函数 sen

  • javascript设计模式之对象工厂函数与构造函数详解

    下面通过文字详解加代码分析的方式给大家分享下javascript设计模式之对象工厂函数与构造函数的相关知识. 概述使用对象字面量,或者向空对象中动态地添加新成员,是最简单易用的对象创建方法.然而,除了这两种常用的对象创建方式,JavaScript还提供了其他方法创建对象.1).使用工厂函数创建对象我们可以编写一个函数,此函数的功能就是创建对象,可将其. 概述 使用对象字面量,或者向空对象中动态地添加新成员,是最简单易用的对象创建方法. 然而,除了这两种常用的对象创建方式,JavaScript还提

  • javascript 用函数实现继承详解

    一.知识储备: 1.枚举属性名称的函数: (1)for...in:可以在循环体中遍历对象中所有可枚举的属性(包括自有属性和继承属性) (2)Object.keys():返回数组(可枚举的自有属性) (3)Object.getOwnPropertyNames():所有的自有属性 3.属性的特性:数据属性和存取器属性 (1)数据属性:可写(writable)  可枚举(enumerable)  可配置(configurable)  值(value) 数据属性只有一个简单的值: (2)存取器属性: 写

  • JavaScript中变量提升和函数提升的详解

    第一篇文章中提到了变量的提升,所以今天就来介绍一下变量提升和函数提升.这个知识点可谓是老生常谈了,不过其中有些细节方面博主很想借此机会,好好总结一下. 今天主要介绍以下几点: 1. 变量提升 2. 函数提升 3. 为什么要进行提升 4. 最佳实践 那么,我们就开始进入主题吧. 1. 变量提升 通常JS引擎会在正式执行之前先进行一次预编译,在这个过程中,首先将变量声明及函数声明提升至当前作用域的顶端,然后进行接下来的处理.(注:当前流行的JS引擎大都对源码进行了编译,由于引擎的不同,编译形式也会有

随机推荐