javascrip是一门函数式的编程语言,了解和熟悉高阶函数对我们掌握这门语言非常有用。
高阶函数相关概念
函数式编程
在大多数简单的术语中,函数编程是一种编程形式,您可以将函数作为参数传递给其他函数,并将它们作为值返回。在函数式编程中,我们根据函数思考和编码。
JavaScript,Haskell,Clojure,Scala和Erlang是实现函数式编程的一些语言。
一等函数
如果您一直在学习JavaScript,您可能听说过JavaScript将函数视为一等公民。那是因为在JavaScript或任何其他函数式编程语言中,函数是对象。
在JavaScript中,函数是一种特殊类型的对象。他们是Function对象。
在JavaScript中,您可以使用其他类型(如对象,字符串或数字)执行的所有操作函数都可以执行。您可以将它们作为参数传递给其他函数(回调函数),将它们分配给变量并传递它们等等。这就是JavaScript中的函数被称为First-Class函数(一等函数)的原因。
高阶函数
Higher-Order function is a function that receives a function as an argument or returns the function as output。
我们将参数或返回值为函数的函数称为高阶函数。
高阶函数的应用
Function 参数
- Array.prototype.map
- Array.prototype.reduce
- Array.prototype.filter
- Array.prototype.sort
我们计算一下数组的平方结果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
//数组的值x2
//普通方法
function normalFunc() {
const arr1 = [1, 2, 3];
const arr2 = [];
for(let i = 0; i < arr1.length; i++) {
arr2.push(arr1[i] * arr1[i]);
}
console.log(arr2) // [1, 4, 9]
}
//高阶函数
function higherOrder() {
const arr1 = [1, 2, 3];
const arr2 = arr1.map( function callback(element, index, array) {
return element*element;
});
console.log(arr2); // [1, 4, 9]
}
//ES6版本
function higherOrderArrow() {
const arr1 = [1, 2, 3];
const arr2 = arr1.map(e => e*e);
console.log(arr2); // [1, 4, 9]
}
normalFunc();
higherOrder();
higherOrderArrow();
</script>
</body>
</html>
上面我们使用的Array.prototype.map就是高阶函数的使用,使用map对数据求平方。
Fucntion 返回值
- Function.prototype.bind
function introduce(firstName) {
return `Hello, I am ${firstName} ${this.lastName}`
}
//
let jason = {lastName: 'Xu'};
let introcucation = introduce.bind(jason);
console.log(introcucation());//Hello, I am Jason Xu
注意我们使用的 Function.prototype.bind返回的是一个函数,如果需要执行需求再次调用
introcucation()方法。
接下来我们自己模拟写一个类似Function.prototype.bind的方法,简单版本的。
function introduce(firstName) {
return `Hello, I am ${firstName} ${this.lastName}`
}
//
let jason = {lastName: 'Xu'};
Function.prototype.audaqueBind = function (context, ...args) {
let _this = this;
return function newFunc() {
return _this.apply(context, ...args, arguments);
}
}
let introcucation = introduce.audaqueBind(jason);
console.log(introcucation('Jason'));//Hello, I am Jason Xu
其实这输出的结果和上面的是一样的:Hello, I am Jason Xu
Function.prototype.audaqueBind()方法实现了Function.prototype.bind的功能这会是不是觉得学习高阶函数很有用了。
如果对bind的用法和作用,详情请看以前写的一篇文章。
javascript Function中 bind()、call()、 apply()用法详解
函数柯里化
在数学和计算机科学中,柯里化是一种将使用多个参数的函数转换成一系列使用一个参数的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
柯里化(currying)又称部分求值。一个currying的函数首先会接受一些参数,接受这些参数之后,函数并不会立即求值,而是继续返回另一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
- 提高函数的适用性
- 实现延迟执行
- 固定易变因素
柯里化可以提前把易变因素,传参固定下来,生成一个更明确的应用函数。
我们来看一下柯里化代码实现
const add = (...args) => args.reduce((a, b) => a + b)
// 简化写法
function currying(func) {
const args = []
return function result(...rest) {
if (rest.length === 0) {
return func(...args)
} else {
args.push(...rest)
return result
}
}
}
const sum = currying(add);
// sum(1, 2)(3); // 未真正求值,收集参数的和
// sum(4);// 未真正求值,收集参数的和
// console.log(sum());// 输出 10
sum(1,2)(3, 4)
console.log(sum());// 输出 10
高阶函数与AOP
AOP为Aspect OrientedProgramming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
(AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。)
主要意图
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。。
例如:日志统计,异常处理等。把这些功能抽离出来后,通过"动态植入"的方法,掺入到业务逻辑模块中。这样做的好处是保证业务逻辑模块的纯净和高内聚,其次可以方便的复用日志统计等功能模块
案例
说了这么多 我们来看一下AOP的使用场景。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
Function.prototype.before = function (beforefn) {
let _self = this;
return function () {
beforefn.apply(this, arguments);
return _self.apply(this, arguments);
}
};
Function.prototype.after = function (afterfn) {
let _self = this;
return function () {
let ret = _self.apply(this, arguments);
afterfn.apply(this, arguments);
return ret;
}
};
let func = function () {
console.log('func');
};
func = func.before(function (){
console.log('before');
}).after(function () {
console.log('after');
});
func();
</script>
</body>
</html>
我们先来看一下浏览器打印的结果:
before
func
after
这是不是很神奇,这样一来我们就可以给我们func这个函数动态的加一些功能了,可以在这个函数执行前后做一些处理。
我们举一个例子,我们帮别人打代理游戏,我们代理主要是来收费。其实核心的还是打游戏就相当于(func函数),至于如何来收费,我们可以动态的去切入,比如我们可以按小时,按天,甚至包月等等服务。我们可以在打代码游戏之前做一些动作,记录开始打游戏的时间,账号等等。在游戏后面可以统计打了多久,如果来计算费用等等。这样是不是很方便。
实现自己的高阶函数
Array.prototype.audaqueMap = function (fn, thisValue) {
let arr = thisValue || this;
let result = []
if (typeof fn !== 'function') {
throw new TypeError(fn + ' is not a function');
}
for (let i = 0; i < arr.length; i++) {
let r = fn.call(arr, arr[i], i, arr)
result.push(r)
}
return result
}
let arr = [4, 9, 16, 25];
let result = arr.audaqueMap((item, index, arr) => {
return item * 2
});
console.log(result);
result = arr.audaqueMap((item, index, arr) => {
return item + 1;
});
console.log(result);
这里我们实现了一个Array.prototype.audaqueMap()方法,这个是不是和Array.prototype.map()实现了相同的功能。这样一来我们可以使用高阶函数来实现对原型方法的加强版本了,这个是不是很有用。