高阶函数也称算子(运算符)或泛函。作为函数式编程最显著的特征,高阶函数是对函数运算进行进一步的抽象。高阶函数的形式应至少满足下列条件之一:
把函数作为值传入另一个参数,当传入参数被调用时,就称为回调函数,即异步调用已绑定的函数。例如,事件处理函数、定时器中的回调函数、异步请求中的回调函数、replace 方法中的替换函数、数组迭代中的回调函数(sort、map、forEach、filter、some、every、reduce 和 reduceRight 等),都是回调函数的不同应用形式。下面仅举两个示例,演示回调函数的应用。
下面代码根据日期对对象进行排序。
//声明3个对象,每个对象都有属性id和date
var a = {id : 1, date : new Date(2019,3,12)},
b = {id : 2, date : new Date(2019,1,14)},
c = {id : 3, date : new Date(2019,2,26)};
var arr = [a,b,c];
arr.sort(function(x,y){
return x.date-y.date;
});
for (var i = 0; i < arr.length; i++) {
console.log(arr[i].id + " " + arr[i].date.toLocaleString());
}
输出结果:
在数组排序的时候,会迭代数组每个元素,并逐一调用回调函数 function(x,y) {return x.date - y.date}。
在《JS map()》一节中我们曾介绍过数组的 map 方法,实际上很多函数式编程语言均有此函数。其语法格式为:
map 表达式将 func 函数作用于 array 的每一个元素,并返回一个新的 array。
下面使用 JavaScript 实现 map(array,func) 表达式运算。
function map(array,func) {
var res = [];
for (var i in array) {
res.push(func(array[i]));
}
return res;
}
console.log(map([1,3,5,7,8], function (n) { //返回元素值的平方
return n * n;
})); //1,9,25,49,64
console.log(map(["one", "two", "three", "four"], function(item) { //返回首字母大写
return item[0].toUpperCase() + item.slice(1).toLowerCase();
})); //One,Two,Three,Four
两次调用 map,却得到了截然不同的结果,是因为 map 的参数本身已经进行了一次抽象,map 函数做的是第二次抽象。注意:高阶的“阶”可以理解为抽象的层次。
JS 函数既可以作为参数传入函数内部,也可以作为返回值 return 到函数外部,具体应用场景包括:
由于篇幅有限,本节只介绍前面三种应用场景,其它场景请猛击链接查看。
单例就是保证一个类只有一个实例。实现方法:先判断实例是否存在,如果存在则直接返回,否则就创建实例再返回。
单例模式可以确保一个类型只有一个实例对象。在 JavaScript 中,单例可以作为一个命名空间,提供一个唯一的访问点来访问该对象。单例模式封装代码如下:
var getSingle = function (fn) {
var ret;
return function () {
return ret || (ret = fn.apply(this, arguments));
};
};
在脚本中定义 XMLHttpRequest 对象。由于一个页面可能需要多次创建异步请求对象,使用单例模式封装之后,就不用重复创建实例对象,共用一个即可。
function XHR () { //定义XMLHttpRequest 对象
return new XMLHttpRequest();
}
var xhr = getSingle(XHR); //封装XHR实例
var a = xhr(); //实例1
var b = xhr(); //实例2
console.log(a === b); //true,说明这两个实例实际上相同
可以限定函数仅能调用一次,避免重复调用,这在事件处理函数中非常有用。
<button>仅能点击一次</button>
<script>
function getSingle (fn) {
var ret;
return function () {
return ret || (ret = fn.apply(this,arguments));
};
};
var f = function () { console.log(this.nodeName); } //事件处理函数
document.getElementsByTagName("button")[0].onclick = getSingle(f);
</script>
AOP(面向切面编程)就是把一些与业务逻辑模块无关的功能抽离出来,如日志统计、安全控制、异常处理等,然后通过“动态织入”的方式掺入业务逻辑模块中。这样设计的好处是:首先可以保证业务逻辑模块的纯净和高内聚性;其次可以方便地复用日志统计等功能模块。
在 JavaScript 中实现 AOP,一般是把一个函数“动态织入”到另外一个函数中。具体的实现方法有很多,下面通过扩展 Function.prototype 方法实现 AOP。
Function.prototype.before = function (beforefn) {
var __self = this; //保存原函数的引用
return function () { //返回包含了原函数和新函数的“代理”函数
beforefn.apply(this, arguments); //执行新函数
return __self.apply(this, arguments); //执行原函数
}
};
Function.prototype.after = function (afterfn) {
var __self = this; //保存原函数的引用
return function () { //返回包含了原函数和新函数的“代理”函数
var ret = __self.apply(this,arguments); //执行原函数
afterfn.apply(this, arguments); //执行新函数,修正this
return ret;
}
};
var func = function (){
console.log(2);
};
func = func.before(function () {
console.log(1);
}).after(function () {
console.log(3)
});
func(); //按顺序输出1,2,3
本节利用 JavaScript 高阶函数特性来重新设计 typeOf() 函数,并提供单项类型判断函数。
function typeOf(obj) { //类型检测函数,返回字符串表示
var str = Object.prototype.toString.call(obj);
return str.match(/\[object(.*?)\]/)[1].toLowerCase();
};
['null', 'Undefined', 'Object', 'Array', 'String', 'Number', 'Boolean', 'Function', 'RegExp'].forEach(function (t) { //类型判断,返回布尔值
typeOf['is' + t] = function (o) {
return typeOf(o) === t.toLowerCase();
};
});
//类型检测
console.log(typeOf({})); //"object"
console.log(typeOf([])); //"array"
console.log(typeOf(0)); //"number"
console.log(typeOf(null)); //"null"
console.log(typeOf(undefined)); //"undefined"
console.log(typeOf(//)); //"regex"
console.log(typeOf(new Date())); //"date"
//类型判断
console.log(typeOf.isObject({})); //true
console.log(typeOf.isNumber(NaN)); //true
console.log(typeOf.isRegExp(true)); //false